package com.moneyhash.reactnativesdk import android.app.Activity import android.content.Intent import androidx.fragment.app.FragmentActivity import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ActivityEventListener import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.moneyhash.sdk.android.common.LogLevel import com.moneyhash.sdk.android.core.IntentContract import com.moneyhash.sdk.android.core.IntentCreationParams import com.moneyhash.sdk.android.core.MoneyHashSDKBuilder import com.moneyhash.sdk.android.googlePay.GooglePayLauncher import com.moneyhash.sdk.android.googlePay.GooglePayResultCallback import com.moneyhash.sdk.android.model.IntentDetails import com.moneyhash.sdk.android.model.receipt.NativePayReceipt import com.moneyhash.sdk.android.model.billingData.CollectibleBillingData import com.moneyhash.sdk.android.bank.BankAccountResultContract import com.moneyhash.shared.errorhandling.ErrorType import com.moneyhash.shared.errorhandling.MHThrowable import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive class MoneyHashModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), ActivityEventListener { private val moneyHashSDK by lazy { MoneyHashSDKBuilder.build() } private var googlePayLauncher: GooglePayLauncher? = null private var googlePayPromise: Promise? = null override fun getName(): String { return NAME } init { reactContext.addActivityEventListener(this) } private var intentDetailsPromise: Promise? = null private var bankAccountPromise: Promise? = null private val intentDetailsContract by lazy { IntentContract() } private val bankAccountContract by lazy { BankAccountResultContract() } @ReactMethod fun getMethods( intentId: String, intentType: String, currency: String, amount: String, customer: String, flowId: String, operation: String, customFields: ReadableMap?, promise: Promise ) { val resultDeferred = executeCatching { if (intentId.isNotEmpty()) { moneyHashSDK.getMethods( intentId = intentId, intentType = DataConverter.getIntentType(intentType) ) } else { val customFieldsMap = DataConverter.getCustomFields(customFields?.toHashMap()) moneyHashSDK.getMethods( currency = currency, amount = amount.toDoubleOrNull(), customerId = customer.ifEmpty { null }, flowId = flowId.ifEmpty { null }, operation = DataConverter.getIntentOperation(operation), customFields = customFieldsMap ) } } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> val data = DataConverter.getIntentMethodJson(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun createCardToken( cardIntentId: String, cardData: ReadableMap, promise: Promise ) { // Validate the card intent ID if (cardIntentId.isEmpty()) { val errorMessage = MHThrowable( "Invalid card intent id", type = ErrorType.UNKNOWN ).extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) return } // Validate the card data val vaultData = DataConverter.getVaultData(cardData.toHashMap()) if (vaultData == null) { val errorMessage = MHThrowable( "Invalid card data", type = ErrorType.UNKNOWN ).extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) return } // Execute the asynchronous operation val resultDeferred = executeCatching { moneyHashSDK.createCardToken( cardIntentId = cardIntentId, cardData = vaultData ) } // Handle the result after the async operation completes resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> // Convert the result to a JSON string val data = DataConverter.getIntentStateDetailsJson(value) promise.resolve(data) // Resolve the result }, onFailure = { error -> // Handle the error case val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun binLookup(promise: Promise) { val resultDeferred = executeCatching { // Call binLookup without cardData moneyHashSDK.binLookup() } // Handle the result after the async operation completes resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> // Convert the result to a JSON string val data = DataConverter.getLookupDataJson(value) promise.resolve(data) // Resolve the result }, onFailure = { error -> // Handle the error case val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun binLookupWithCardData(cardData: ReadableMap, promise: Promise) { // Validate the card data val vaultData = DataConverter.getVaultData(cardData.toHashMap()) if (vaultData == null) { val errorMessage = MHThrowable( "Invalid card data", type = ErrorType.UNKNOWN ).extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) return } val resultDeferred = executeCatching { // Call binLookup with cardData moneyHashSDK.binLookup(cardData = vaultData) } // Handle the result after the async operation completes resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> // Convert the result to a JSON string val data = DataConverter.getLookupDataJson(value) promise.resolve(data) // Resolve the result }, onFailure = { error -> // Handle the error case val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun getIntentDetails(intentId: String, intentType: String, promise: Promise) { val resultDeferred = executeCatching { moneyHashSDK.getIntentDetails( intentId, DataConverter.getIntentType(intentType) ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> val data = DataConverter.getIntentDetailsJson(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun resetSelectedMethod(intentId: String, intentType: String, promise: Promise) { val resultDeferred = executeCatching { moneyHashSDK.resetSelectedMethod( intentId, DataConverter.getIntentType(intentType) ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> val data = DataConverter.getIntentMethodResultJson(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun deleteSavedCard(cardTokenId: String, intentSecret: String, promise: Promise) { val resultDeferred = executeCatching { moneyHashSDK.deleteSavedCard( cardTokenId, intentSecret ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { promise.resolve(null) // Assuming success returns no data, just resolving with null }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun proceedWithMethod( intentId: String, intentType: String, selectedMethodId: String, methodType: String, methodMetaData: ReadableMap, useWalletBalance: Boolean, installmentPlanData: ReadableMap, promise: Promise ) { val resultDeferred = executeCatching { moneyHashSDK.proceedWithMethod( intentId, DataConverter.getIntentType(intentType), selectedMethodId, DataConverter.getMethodType(methodType), DataConverter.getMethodMetaData(methodMetaData.toHashMap()), useWalletBalance, installmentPlanData = DataConverter.getInstallmentPlanData(installmentPlanData.toHashMap()) ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> val data = DataConverter.getIntentMethodResultJson(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun renderForm( intentId: String, intentType: String, embedStyle: ReadableMap, promise: Promise ) { val activity = reactApplicationContext.currentActivity ?: throw IllegalStateException("Activity is null") val intentParams = IntentCreationParams( intentId = intentId, intentType = DataConverter.getIntentType(intentType), embedStyle = DataConverter.getEmbedStyle(embedStyle.toHashMap()) ) val intent = intentDetailsContract.createIntent(activity, intentParams) intentDetailsPromise = promise activity.startActivityForResult(intent, INTENT_DETAILS_REQUEST_CODE) } @ReactMethod fun renderSubscriptionEmbed( intentId: String, embedStyle: ReadableMap, promise: Promise ) { val activity = reactApplicationContext.currentActivity ?: throw IllegalStateException("Activity is null") val embedStyleHashMap = embedStyle.toHashMap() // Check if the map is empty. If yes, pass null; otherwise, process it. val embedStyleValue = if (embedStyleHashMap.isEmpty()) null else DataConverter.getEmbedStyle(embedStyleHashMap) val intentParams = IntentCreationParams( intentId = intentId, intentType = DataConverter.getIntentType("payment"), // Default to payment type for subscriptions embedStyle = embedStyleValue, isSubscriptionEmbed = true ) val intent = intentDetailsContract.createIntent(activity, intentParams) intentDetailsPromise = promise activity.startActivityForResult(intent, INTENT_DETAILS_REQUEST_CODE) } @ReactMethod fun renderURL( url: String, intentId: String, intentType: String, embedStyle: ReadableMap, promise: Promise ) { val activity = reactApplicationContext.currentActivity ?: throw IllegalStateException("Activity is null") val embedStyleHashMap = embedStyle.toHashMap() // Check if the map is empty. If yes, pass null; otherwise, process it. val embedStyleValue = if (embedStyleHashMap.isEmpty()) null else DataConverter.getEmbedStyle(embedStyleHashMap) val intentParams = IntentCreationParams( url = url, isUrlRendering = true, intentId = intentId, intentType = DataConverter.getIntentType(intentType), embedStyle = embedStyleValue ) val intent = intentDetailsContract.createIntent(activity, intentParams) intentDetailsPromise = promise activity.startActivityForResult(intent, INTENT_DETAILS_REQUEST_CODE) } @ReactMethod fun setLogLevel(logLevel: String, promise: Promise) { val logLevel = LogLevel.from(logLevel) moneyHashSDK.setLogLevel(logLevel) promise.resolve(null) // Log level set successfully, no data to return } @ReactMethod fun setLocale(locale: String, promise: Promise) { moneyHashSDK.setLocale(DataConverter.getLocale(locale)) promise.resolve(null) // locale set successfully, no data to return } @ReactMethod fun setPublicKey(publicKey: String, promise: Promise) { moneyHashSDK.setPublicKey(publicKey) promise.resolve(null) } @ReactMethod fun setVersionName(versionName: String, promise: Promise) { moneyHashSDK.setVersionName(versionName) promise.resolve(null) } @ReactMethod fun submitForm( intentId: String, selectedMethodId: String, billingData: ReadableMap?, shippingData: ReadableMap?, cardData: ReadableMap, saveCard: Boolean, installmentPlanData: ReadableMap, promise: Promise ) { val billing = billingData?.toHashMap()?.map { it.key to it.value.toString() }?.toMap() val shipping = shippingData?.toHashMap()?.map { it.key to it.value.toString() }?.toMap() val resultDeferred = executeCatching { moneyHashSDK.submitForm( intentId = intentId, selectedMethod = selectedMethodId, billingData = billing, shippingData = shipping, cardData = DataConverter.getVaultData(cardData.toHashMap()), saveCard = saveCard, installmentPlanData = DataConverter.getInstallmentPlanData(installmentPlanData.toHashMap()) ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> val data = DataConverter.getIntentDetailsJson(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun updateFees( intentId: String, fees: ReadableArray, promise: Promise ) { val feesItems = try{ DataConverter.getFeesItems(fees.toArrayList() as? List>) } catch (throwable: Throwable) { val errorMessage = MHThrowable("Invalid fees data", listOf(), ErrorType.UNKNOWN).extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) return } val resultDeferred = executeCatching { moneyHashSDK.updateFees( intentId = intentId, fees = feesItems ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> val data = DataConverter.getFeesDataJson(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun updateDiscount( intentId: String, discount: ReadableMap, promise: Promise ) { val discountItem = DataConverter.getDiscountItem(discount.toHashMap()) if (discountItem == null) { val errorMessage = MHThrowable("Invalid discount data", listOf(), ErrorType.UNKNOWN).extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) return } val resultDeferred = executeCatching { moneyHashSDK.updateDiscount( intentId = intentId, discount = discountItem ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> val data = DataConverter.getDiscountDataJson(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun submitCardCVV( intentId: String, cvv: String, installmentPlanData: ReadableMap, promise: Promise ) { val resultDeferred = executeCatching { moneyHashSDK.submitCardCVV( intentId = intentId, cvv = cvv, installmentPlanData = DataConverter.getInstallmentPlanData(installmentPlanData.toHashMap()) ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> val data = DataConverter.getIntentDetailsJson(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun submitPaymentReceipt(intentId: String, receipt: ReadableMap, promise: Promise) { val receipt = DataConverter.getNativePayReceipt(receipt.toHashMap()) if (receipt == null) { val errorMessage = MHThrowable("Invalid receipt data", listOf(), ErrorType.UNKNOWN).extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) return } val resultDeferred = executeCatching { moneyHashSDK.submitPaymentReceipt( intentId = intentId, receipt = receipt ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> val responseData = DataConverter.getIntentDetailsJson(value) promise.resolve(responseData) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun updateCardField(fieldType: String, value: String, promise: Promise) { // Update only: store the value and refresh the brand. The field state is // fetched separately via `validateCardField`. val resultDeferred = executeCatching { moneyHashSDK.updateCardField( fieldType = DataConverter.getFieldType(fieldType), currentValue = value ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { promise.resolve("success") }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun validateCardField(fieldType: String, promise: Promise) { val resultDeferred = executeCatching { moneyHashSDK.validateCardField( fieldType = DataConverter.getFieldType(fieldType) ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { validationResult -> val data = DataConverter.getCardFieldStateJson(validationResult) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun isValidCardForm(promise: Promise) { val resultDeferred = executeCatching { moneyHashSDK.isValidCardForm() } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { validationResult -> val data = DataConverter.getCardFormStateJson(validationResult) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun collectCardForm( token: String?, promise: Promise ) { val resultDeferred = executeCatching { moneyHashSDK.collectCardForm(token = token) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> val data = DataConverter.getVaultDataJson(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun pay( intentId: String, cardData: ReadableMap, saveCard: Boolean, billingData: ReadableMap?, shippingData: ReadableMap?, installmentPlanData: ReadableMap, promise: Promise ) { // Convert the card data using the DataConverter val vaultData = DataConverter.getVaultData(cardData.toHashMap()) if (vaultData == null) { // Handle invalid card data val errorMessage = MHThrowable( "Invalid card data", type = ErrorType.UNKNOWN ).extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) return } // Execute the payment asynchronously val resultDeferred = executeCatching { moneyHashSDK.pay( intentId = intentId, cardData = vaultData, saveCard = saveCard, billingData = (billingData?.toHashMap() as? Map).orEmpty(), shippingData = (shippingData?.toHashMap() as? Map).orEmpty(), installmentPlanData = DataConverter.getInstallmentPlanData(installmentPlanData.toHashMap()) ) } // Handle the result after the async operation completes resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> // Convert the result to a JSON string val data = DataConverter.getIntentDetailsJson(value) println("DataConverter Success: $data") promise.resolve(data) // Resolve the result }, onFailure = { error -> // Handle the error case val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun getInstallmentPlans( amount: Double, currency: String, first6Digits: String, promise: Promise ) { val resultDeferred = executeCatching { moneyHashSDK.getInstallmentPlans( amount = amount, currency = currency, first6Digits = first6Digits.nullIfEmpty() ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> val data = Json.encodeToString(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun setCardFormConfiguration(configuration: ReadableMap, promise: Promise) { // Convert the passed ReadableMap into a HashMap val configMap = configuration.toHashMap() // Convert to your CardFormConfiguration model val cardFormConfiguration = DataConverter.getCardFormConfiguration(configMap) if (cardFormConfiguration == null) { // Return error if conversion fails val errorMessage = MHThrowable("Invalid Card Form Configuration", listOf(), ErrorType.UNKNOWN).extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) return } // Assuming your MoneyHashSDK supports a setCardFormConfiguration function: moneyHashSDK.setCardFormConfiguration(cardFormConfiguration) // Return success or any response you see fit promise.resolve("Success") } @ReactMethod fun isReadyForGooglePay(promise: Promise) { if(googlePayLauncher == null) { createGooglePayLauncher() } val resultDeferred = executeCatching { googlePayLauncher?.isReady() } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> promise.resolve(value) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun presentForGooglePay( intentId: String, currency: String, countryCode: String, amount: Double, gateway: String, gatewayMerchantId: String, merchantId: String, merchantName: String, promise: Promise ) { if(googlePayLauncher == null) { createGooglePayLauncher() } googlePayPromise = promise googlePayLauncher?.presentForPaymentIntent( intentId = intentId, currency = currency, countryCode = countryCode, amount = amount, gateway = gateway, gatewayMerchantId = gatewayMerchantId, merchantId = merchantId, merchantName = merchantName ) } @ReactMethod fun getSubscriptionPlans( planGroupId: String, customerId: String, promise: Promise ) { val resultDeferred = executeCatching { moneyHashSDK.getSubscriptionPlans(planGroupId, customerId) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> val data = Json.encodeToString(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun selectSubscriptionPlan( planGroupId: String, customerId: String, planId: String, promise: Promise ) { val resultDeferred = executeCatching { moneyHashSDK.selectSubscriptionPlan(planGroupId, customerId, planId) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> // Convert result to JSON string or any suitable RN return type val data = DataConverter.getIntentDetailsJson(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun getSubscriptionPlanGroups( limit: Int, offset: Int, currency: String, promise: Promise ) { val resultDeferred = executeCatching { moneyHashSDK.getSubscriptionPlanGroups( limit = if (limit == 0) null else limit, offset = if (offset == 0) null else offset, currency = currency.ifEmpty { null } ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> val data = Json.encodeToString(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun selectInstallmentPlan( intentId: String, installmentPlanData: ReadableMap, promise: Promise ) { // Convert the JS object to your Kotlin data structure: val installmentPlan = DataConverter.getInstallmentPlanData(installmentPlanData.toHashMap()) // Validate if (installmentPlan == null) { val errorMessage = MHThrowable( "Invalid installment plan data", type = ErrorType.UNKNOWN ).extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) return } val resultDeferred = executeCatching { moneyHashSDK.selectInstallmentPlan( intentId = intentId, installmentPlanData = installmentPlan ) } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { value -> // Convert result to JSON string or any suitable RN return type val data = DataConverter.getIntentDetailsJson(value) promise.resolve(data) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } @ReactMethod fun setNativeGooglePayConfig(googlePayConfig: ReadableMap, promise: Promise) { try { val config = DataConverter.getNativeGooglePayConfig(googlePayConfig.toHashMap()) ?: return moneyHashSDK.setNativeGooglePayConfig(config) } catch (e: Exception) { val errorMessage = e.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } } @ReactMethod fun renderCreateBankAccountTokenEmbed( intentId: String, promise: Promise ) { try { val activity = reactApplicationContext.currentActivity ?: throw IllegalStateException("Activity is null") bankAccountPromise = promise // Create the intent using the contract directly val intent = bankAccountContract.createIntent(activity, intentId) activity.startActivityForResult(intent, BANK_ACCOUNT_REQUEST_CODE) } catch (e: Exception) { val errorMessage = e.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) bankAccountPromise = null } } @ReactMethod fun isGooglePayReady( allowedCardNetworks: ReadableArray, allowedCardAuthMethods: ReadableArray, promise: Promise ) { val networks = mutableListOf() for (i in 0 until allowedCardNetworks.size()) { allowedCardNetworks.getString(i)?.let { networks.add(it) } } val authMethods = mutableListOf() for (i in 0 until allowedCardAuthMethods.size()) { allowedCardAuthMethods.getString(i)?.let { authMethods.add(it) } } // Run on UI thread reactApplicationContext.currentActivity?.runOnUiThread { try { initializeGooglePayLauncher() val resultDeferred = executeCatching { googlePayLauncher?.isReady( allowedCardNetworks = if (networks.isEmpty()) null else networks, allowedCardAuthMethods = if (authMethods.isEmpty()) null else authMethods ) ?: false } resultDeferred.invokeOnCompletion { val dataResult = resultDeferred.getCompleted() dataResult.fold( onSuccess = { isReady -> promise.resolve(isReady) }, onFailure = { error -> val errorMessage = error.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } ) } } catch (e: Exception) { val errorMessage = e.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) } } ?: run { promise.reject("NO_ACTIVITY", "No current activity available") } } @ReactMethod fun proceedWithGooglePay( intentId: String, currency: String, amount: Double, countryCode: String, gateway: String, gatewayMerchantId: String, merchantId: String, merchantName: String, allowedCardNetworks: ReadableArray, allowedCardAuthMethods: ReadableArray, promise: Promise ) { val networks = mutableListOf() for (i in 0 until allowedCardNetworks.size()) { allowedCardNetworks.getString(i)?.let { networks.add(it) } } val authMethods = mutableListOf() for (i in 0 until allowedCardAuthMethods.size()) { allowedCardAuthMethods.getString(i)?.let { authMethods.add(it) } } // Run on UI thread reactApplicationContext.currentActivity?.runOnUiThread { try { initializeGooglePayLauncher() googlePayPromise = promise googlePayLauncher?.presentForPaymentIntent( intentId = intentId, currency = currency, countryCode = countryCode, amount = amount, gateway = gateway, gatewayMerchantId = gatewayMerchantId, merchantId = merchantId, merchantName = merchantName, allowedCardNetworks = if (networks.isEmpty()) null else networks, allowedCardAuthMethods = if (authMethods.isEmpty()) null else authMethods ) } catch (e: Exception) { val errorMessage = e.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) googlePayPromise = null } } ?: run { promise.reject("NO_ACTIVITY", "No current activity available") } } @ReactMethod fun generateGooglePayReceipt( currency: String, amount: Double, countryCode: String, gateway: String, gatewayMerchantId: String, merchantId: String, merchantName: String, allowedCardNetworks: ReadableArray, allowedCardAuthMethods: ReadableArray, promise: Promise ) { val networks = mutableListOf() for (i in 0 until allowedCardNetworks.size()) { allowedCardNetworks.getString(i)?.let { networks.add(it) } } val authMethods = mutableListOf() for (i in 0 until allowedCardAuthMethods.size()) { allowedCardAuthMethods.getString(i)?.let { authMethods.add(it) } } // Run on UI thread reactApplicationContext.currentActivity?.runOnUiThread { try { initializeGooglePayLauncher() googlePayPromise = promise googlePayLauncher?.generateGooglePayReceipt( currency = currency, countryCode = countryCode, amount = amount, gateway = gateway, gatewayMerchantId = gatewayMerchantId, merchantId = merchantId, merchantName = merchantName, allowedCardNetworks = if (networks.isEmpty()) null else networks, allowedCardAuthMethods = if (authMethods.isEmpty()) null else authMethods ) } catch (e: Exception) { val errorMessage = e.extractErrorMessage() promise.reject(errorMessage.first, errorMessage.second) googlePayPromise = null } } ?: run { promise.reject("NO_ACTIVITY", "No current activity available") } } private fun initializeGooglePayLauncher() { if (googlePayLauncher == null) { val activity = reactApplicationContext.currentActivity if (activity is androidx.fragment.app.FragmentActivity) { googlePayLauncher = GooglePayLauncher( activity = activity, resultCallback = object : GooglePayResultCallback { override fun onResult(result: IntentDetails) { googlePayPromise?.resolve(DataConverter.getIntentDetailsJson(result)) googlePayPromise = null } override fun onResult(result: NativePayReceipt) { val resultJson =DataConverter.getNativePayReceiptJson(result) googlePayPromise?.resolve(resultJson) googlePayPromise = null } override fun onError(throwable: Throwable) { val errorMessage = throwable.extractErrorMessage() googlePayPromise?.reject(errorMessage.first, errorMessage.second) googlePayPromise = null } override fun onCancel() { val errorMessage = MHThrowable( "Google Pay payment was cancelled by the user", type = ErrorType.CANCELLED ).extractErrorMessage() googlePayPromise?.reject(errorMessage.first, errorMessage.second) googlePayPromise = null } } ) } else { throw IllegalStateException("Activity must be a FragmentActivity to use Google Pay") } } } override fun onActivityResult( activity: Activity, requestCode: Int, resultCode: Int, data: Intent? ) { if (requestCode == INTENT_DETAILS_REQUEST_CODE) { val result = intentDetailsContract.parseResult(resultCode, data) if (result != null) { intentDetailsPromise?.resolve(DataConverter.getIntentDetailsJson(result)) } else { val errorMessage = MHThrowable("Could not get intent details, User cancelled or something went wrong", type = ErrorType.UNKNOWN).extractErrorMessage() intentDetailsPromise?.reject(errorMessage.first, errorMessage.second) } intentDetailsPromise = null } else if (requestCode == BANK_ACCOUNT_REQUEST_CODE) { val result = bankAccountContract.parseResult(resultCode, data) if (result != null) { bankAccountPromise?.resolve(DataConverter.getBankAccountStatusJson(result)) } else { val errorMessage = MHThrowable("Bank account tokenization failed or cancelled", type = ErrorType.UNKNOWN).extractErrorMessage() bankAccountPromise?.reject(errorMessage.first, errorMessage.second) } bankAccountPromise = null } } override fun onNewIntent(intent: Intent) { // NOT USED } private fun createGooglePayLauncher() { val activity = reactApplicationContext.currentActivity as? FragmentActivity ?: throw IllegalStateException("Activity is null") googlePayLauncher = GooglePayLauncher( activity = activity, resultCallback = object : GooglePayResultCallback { override fun onResult(result: IntentDetails) { googlePayPromise?.resolve(DataConverter.getIntentDetailsJson(result)) googlePayPromise = null } override fun onResult(result: NativePayReceipt) { googlePayPromise?.resolve(DataConverter.getNativePayReceiptJson(result)) googlePayPromise = null } override fun onError(throwable: Throwable) { val errorMessage = throwable.extractErrorMessage() googlePayPromise?.reject(errorMessage.first, errorMessage.second) googlePayPromise = null } override fun onCancel() { val errorMessage = MHThrowable( "Google Pay operation cancelled", type = ErrorType.CANCELLED ).extractErrorMessage() googlePayPromise?.reject(errorMessage.first, errorMessage.second) googlePayPromise = null } } ) } fun onHostResume() { // Re-initialize Google Pay launcher if needed when activity resumes if (googlePayPromise != null) { initializeGooglePayLauncher() } } fun onHostPause() { // Clean up if needed } fun onHostDestroy() { // Clean up Google Pay resources googlePayLauncher = null googlePayPromise = null } companion object { const val NAME = "MoneyHash" private const val INTENT_DETAILS_REQUEST_CODE = 9045 private const val BANK_ACCOUNT_REQUEST_CODE = 9046 } } // Generic function to handle suspend functions and return either the result or an error message private fun executeCatching( scope: CoroutineScope = GlobalScope, // Default to GlobalScope but can be replaced with any scope block: suspend () -> T ): Deferred> { val deferred = CompletableDeferred>() scope.launch(Dispatchers.IO) { try { val result = block() deferred.complete(Result.success(result)) } catch (throwable: Throwable) { deferred.complete(Result.failure(throwable)) } } return deferred } private fun Throwable.extractErrorMessage(type: ErrorType? = null): Pair { val jsonError = mutableMapOf() if (this is MHThrowable) { jsonError["message"] = this.message?.let { JsonPrimitive(it) } ?: JsonNull jsonError["errors"] = this.errors.map { JsonObject( mapOf( "key" to JsonPrimitive(it.key), "message" to JsonPrimitive(it.message) ) ) }.toJsonArray() jsonError["type"] = this.type.let { Json.encodeToString(it) }.let { JsonPrimitive(it) } } else { jsonError["message"] = this.message?.let { JsonPrimitive(it) } ?: JsonNull jsonError["type"] = JsonPrimitive((type ?: ErrorType.UNKNOWN).let { Json.encodeToString(it) }) } return this.message.orEmpty() to Json.encodeToString(JsonObject(jsonError)) } fun String.nullIfEmpty(): String? { return this.ifEmpty { null } } fun Map<*,*>.nullIfEmpty(): Map<*,*>? { return ifEmpty { null } }