package com.reactnativestripesdk.pushprovisioning import android.app.Activity import android.util.Log import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableMap import com.google.android.gms.tasks.Task import com.reactnativestripesdk.utils.createError import com.reactnativestripesdk.utils.getIntOr typealias TokenCheckHandler = (isCardInWallet: Boolean, token: WritableMap?, error: WritableMap?) -> Unit object TapAndPayProxy { private const val TAG = "StripeTapAndPay" private var tapAndPayClient: Any? = null const val REQUEST_CODE_TOKENIZE = 90909 private fun getTapandPayTokens(activity: Activity): Task>? = try { val tapAndPayClass = Class.forName("com.google.android.gms.tapandpay.TapAndPay") val getClientMethod = tapAndPayClass.getMethod("getClient", Activity::class.java) val client = getClientMethod.invoke(null, activity) val tapAndPayClientClass = Class.forName("com.google.android.gms.tapandpay.TapAndPayClient") val listTokensMethod = tapAndPayClientClass.getMethod("listTokens") @Suppress("UNCHECKED_CAST") listTokensMethod.invoke(client) as Task> } catch (e: Exception) { Log.e(TAG, "There was a problem listing tokens with Google TapAndPay: " + e.message) null } internal fun isTokenInWallet( token: Any, newLastFour: String, ): Boolean = try { val getFpanLastFourMethod = Class .forName("com.google.android.gms.tapandpay.issuer.TokenInfo") .getMethod("getFpanLastFour") val existingFpanLastFour = getFpanLastFourMethod.invoke(token) as String existingFpanLastFour == newLastFour } catch (e: Exception) { Log.e(TAG, "There was a problem getting the FPAN with Google TapAndPay: " + e.message) false } fun findExistingToken( activity: Activity, newCardLastFour: String, callback: TokenCheckHandler, ) { val tokens = getTapandPayTokens(activity) if (tokens == null) { callback(false, null, createError("Failed", "Google TapAndPay dependency not found.")) return } tokens.addOnCompleteListener { task -> if (task.isSuccessful) { for (token in task.result) { if (isTokenInWallet(token, newCardLastFour)) { callback(true, mapFromTokenInfo(token), null) return@addOnCompleteListener } } } else { Log.e(TAG, "Unable to fetch existing tokens from Google TapAndPay.") } callback(false, null, null) } } fun tokenize( activity: Activity, tokenReferenceId: String, token: ReadableMap, cardDescription: String, ) { try { val tapAndPayClientClass = Class.forName("com.google.android.gms.tapandpay.TapAndPayClient") val tokenizeMethod = tapAndPayClientClass::class .java .getMethod( "tokenize", Activity::class.java, String::class.java, Int::class.java, String::class.java, Int::class.java, Int::class.java, ) tokenizeMethod.invoke( tapAndPayClient, activity, tokenReferenceId, token.getIntOr("serviceProvider", 0), cardDescription, token.getIntOr("network", 0), REQUEST_CODE_TOKENIZE, ) } catch (e: Exception) { Log.e(TAG, "There was a problem tokenizing with Google TapAndPay: " + e.message) } } private fun mapFromTokenInfo(token: Any?): WritableMap { val result = Arguments.createMap() token?.let { try { val tokenInfoClass = Class.forName("com.google.android.gms.tapandpay.issuer.TokenInfo") result.putString("id", tokenInfoClass.getMethod("getIssuerTokenId").invoke(it) as String) val fpan = tokenInfoClass.getMethod("getFpanLastFour").invoke(it) as String result.putString("cardLastFour", fpan) result.putString("fpanLastFour", fpan) result.putString( "dpanLastFour", tokenInfoClass.getMethod("getDpanLastFour").invoke(it) as String, ) result.putString("issuer", tokenInfoClass.getMethod("getIssuerName").invoke(it) as String) result.putString( "status", mapFromTokenState(tokenInfoClass.getMethod("getTokenState").invoke(it) as Int), ) result.putInt("network", tokenInfoClass.getMethod("getNetwork").invoke(it) as Int) result.putInt( "serviceProvider", tokenInfoClass.getMethod("getTokenServiceProvider").invoke(it) as Int, ) } catch (e: Exception) { Log.e( TAG, "There was a problem mapping the token information with Google TapAndPay: " + e.message, ) } } return result } private fun mapFromTokenState(status: Int): String { try { val tapAndPayClass = Class.forName("com.google.android.gms.tapandpay.TapAndPay") return when (status) { tapAndPayClass.getField("TOKEN_STATE_NEEDS_IDENTITY_VERIFICATION").get(tapAndPayClass) -> "TOKEN_STATE_NEEDS_IDENTITY_VERIFICATION" tapAndPayClass.getField("TOKEN_STATE_PENDING").get(tapAndPayClass) -> "TOKEN_STATE_PENDING" tapAndPayClass.getField("TOKEN_STATE_SUSPENDED").get(tapAndPayClass) -> "TOKEN_STATE_SUSPENDED" tapAndPayClass.getField("TOKEN_STATE_ACTIVE").get(tapAndPayClass) -> "TOKEN_STATE_ACTIVE" tapAndPayClass.getField("TOKEN_STATE_FELICA_PENDING_PROVISIONING").get(tapAndPayClass) -> "TOKEN_STATE_FELICA_PENDING_PROVISIONING" tapAndPayClass.getField("TOKEN_STATE_UNTOKENIZED").get(tapAndPayClass) -> "TOKEN_STATE_UNTOKENIZED" else -> "UNKNOWN" } } catch (e: Exception) { Log.e(TAG, "There was a problem mapping the token state with Google TapAndPay: " + e.message) return "UNKNOWN" } } }