Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Stripe.createRadarSession() API binding #3737

Merged
merged 6 commits into from
May 20, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions stripe/api/stripe.api
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,9 @@ public final class com/stripe/android/Stripe {
public final fun createPiiTokenSynchronous (Ljava/lang/String;Ljava/lang/String;)Lcom/stripe/android/model/Token;
public final fun createPiiTokenSynchronous (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/stripe/android/model/Token;
public static synthetic fun createPiiTokenSynchronous$default (Lcom/stripe/android/Stripe;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/stripe/android/model/Token;
public final fun createRadarSession (Lcom/stripe/android/ApiResultCallback;)V
public final fun createRadarSession (Ljava/lang/String;Lcom/stripe/android/ApiResultCallback;)V
public static synthetic fun createRadarSession$default (Lcom/stripe/android/Stripe;Ljava/lang/String;Lcom/stripe/android/ApiResultCallback;ILjava/lang/Object;)V
public final fun createSource (Lcom/stripe/android/model/SourceParams;Lcom/stripe/android/ApiResultCallback;)V
public final fun createSource (Lcom/stripe/android/model/SourceParams;Ljava/lang/String;Lcom/stripe/android/ApiResultCallback;)V
public final fun createSource (Lcom/stripe/android/model/SourceParams;Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/ApiResultCallback;)V
Expand Down Expand Up @@ -1083,6 +1086,7 @@ public final class com/stripe/android/StripeKtxKt {
public static synthetic fun createPersonToken$default (Lcom/stripe/android/Stripe;Lcom/stripe/android/model/PersonTokenParams;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static final fun createPiiToken (Lcom/stripe/android/Stripe;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun createPiiToken$default (Lcom/stripe/android/Stripe;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static final fun createRadarSession (Lcom/stripe/android/Stripe;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun createSource (Lcom/stripe/android/Stripe;Lcom/stripe/android/model/SourceParams;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun createSource$default (Lcom/stripe/android/Stripe;Lcom/stripe/android/model/SourceParams;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static final fun getAuthenticateSourceResult (Lcom/stripe/android/Stripe;ILandroid/content/Intent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down Expand Up @@ -4092,6 +4096,28 @@ public final class com/stripe/android/model/PiiTokenParams$Creator : android/os/
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/model/RadarSession : com/stripe/android/model/StripeModel {
public static final field CREATOR Landroid/os/Parcelable$Creator;
public fun <init> (Ljava/lang/String;)V
public final fun component1 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;)Lcom/stripe/android/model/RadarSession;
public static synthetic fun copy$default (Lcom/stripe/android/model/RadarSession;Ljava/lang/String;ILjava/lang/Object;)Lcom/stripe/android/model/RadarSession;
public fun describeContents ()I
public fun equals (Ljava/lang/Object;)Z
public final fun getId ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun writeToParcel (Landroid/os/Parcel;I)V
}

public final class com/stripe/android/model/RadarSession$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/model/RadarSession;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lcom/stripe/android/model/RadarSession;
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/model/SetupIntent : com/stripe/android/model/StripeIntent {
public static final field CREATOR Landroid/os/Parcelable$Creator;
public static final field Companion Lcom/stripe/android/model/SetupIntent$Companion;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,43 @@
package com.stripe.android

import android.content.Context
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import com.stripe.android.networking.FingerprintRequestExecutor
import com.stripe.android.networking.FingerprintRequestFactory
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Calendar
import kotlin.coroutines.CoroutineContext

internal interface FingerprintDataRepository {
@UiThread
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be @WorkerThread ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@UiThread because it is safe to call from the main thread.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm from the doc a method is annotated with @UiThread only when it has to be run on ui thread - but it seems #refresh doesn't have to be called on ui thread right?

fun refresh()
fun get(): FingerprintData?

/**
* Get the cached [FingerprintData]. This is not a blocking request.
*/
@UiThread
fun getCached(): FingerprintData?

/**
* Get the latest [FingerprintData]. This is a blocking request.
*
* 1. From [FingerprintDataStore] if that value is not expired.
* 2. Otherwise, from the network.
*/
@WorkerThread
suspend fun getLatest(): FingerprintData?

@UiThread
fun save(fingerprintData: FingerprintData)

class Default(
private val localStore: FingerprintDataStore,
private val fingerprintRequestFactory: FingerprintRequestFactory,
private val fingerprintRequestExecutor: FingerprintRequestExecutor =
FingerprintRequestExecutor.Default(),
private val fingerprintRequestExecutor: FingerprintRequestExecutor,
private val workContext: CoroutineContext
) : FingerprintDataRepository {
private var cachedFingerprintData: FingerprintData? = null
Expand All @@ -27,41 +46,50 @@ internal interface FingerprintDataRepository {
Calendar.getInstance().timeInMillis
}

@JvmOverloads
constructor(
context: Context
context: Context,
workContext: CoroutineContext = Dispatchers.IO
) : this(
localStore = FingerprintDataStore.Default(context),
localStore = FingerprintDataStore.Default(context, workContext),
fingerprintRequestFactory = FingerprintRequestFactory.Default(context),
workContext = Dispatchers.IO
fingerprintRequestExecutor = FingerprintRequestExecutor.Default(
workContext = workContext
),
workContext = workContext
)

override fun refresh() {
if (Stripe.advancedFraudSignalsEnabled) {
CoroutineScope(workContext).launch {
localStore.get().let { localFingerprintData ->
if (localFingerprintData == null ||
localFingerprintData.isExpired(timestampSupplier())
) {
fingerprintRequestExecutor.execute(
request = fingerprintRequestFactory.create(
localFingerprintData
)
)
} else {
getLatest()
}
}
}

override suspend fun getLatest() = withContext(workContext) {
val latestFingerprintData = localStore.get().let { localFingerprintData ->
if (localFingerprintData == null ||
localFingerprintData.isExpired(timestampSupplier())
) {
fingerprintRequestExecutor.execute(
request = fingerprintRequestFactory.create(
localFingerprintData
}
}.let { fingerprintData ->
if (cachedFingerprintData != fingerprintData) {
fingerprintData?.let {
save(it)
}
}
}
)
)
} else {
localFingerprintData
}
}

if (cachedFingerprintData != latestFingerprintData) {
latestFingerprintData?.let(::save)
}

latestFingerprintData
}

override fun get(): FingerprintData? {
override fun getCached(): FingerprintData? {
return cachedFingerprintData.takeIf {
Stripe.advancedFraudSignalsEnabled
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package com.stripe.android

import android.content.Context
import androidx.core.content.edit
import com.stripe.android.model.parsers.FingerprintDataJsonParser
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
import kotlin.coroutines.CoroutineContext

internal interface FingerprintDataStore {
suspend fun get(): FingerprintData?
fun save(fingerprintData: FingerprintData)

class Default(
context: Context,
private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO
private val workContext: CoroutineContext = Dispatchers.IO
) : FingerprintDataStore {
private val prefs by lazy {
context.getSharedPreferences(
Expand All @@ -22,7 +23,7 @@ internal interface FingerprintDataStore {
)
}

override suspend fun get() = withContext(coroutineDispatcher) {
override suspend fun get() = withContext(workContext) {
runCatching {
val json = JSONObject(prefs.getString(KEY_DATA, null).orEmpty())
val timestampSupplier = {
Expand All @@ -33,9 +34,9 @@ internal interface FingerprintDataStore {
}

override fun save(fingerprintData: FingerprintData) {
prefs.edit()
.putString(KEY_DATA, fingerprintData.toJson().toString())
.apply()
prefs.edit {
putString(KEY_DATA, fingerprintData.toJson().toString())
}
}

private companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ data class PaymentConfiguration internal constructor(
/**
* Manages saving and loading [PaymentConfiguration] data to SharedPreferences.
*/
private class Store internal constructor(context: Context) {
private class Store(context: Context) {
private val prefs: SharedPreferences =
context.applicationContext.getSharedPreferences(NAME, 0)

@JvmSynthetic
internal fun save(
fun save(
publishableKey: String,
stripeAccountId: String?
) {
Expand Down
24 changes: 24 additions & 0 deletions stripe/src/main/java/com/stripe/android/Stripe.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.PersonTokenParams
import com.stripe.android.model.PiiTokenParams
import com.stripe.android.model.RadarSession
import com.stripe.android.model.SetupIntent
import com.stripe.android.model.Source
import com.stripe.android.model.SourceParams
Expand Down Expand Up @@ -2006,6 +2007,29 @@ class Stripe internal constructor(
}
}

/**
* Create a Radar Session asynchronously
mshafrir-stripe marked this conversation as resolved.
Show resolved Hide resolved
*
* @param stripeAccountId Optional, the Connect account to associate with this request.
* By default, will use the Connect account that was used to instantiate the `Stripe` object, if specified.
* @param callback a [ApiResultCallback] to receive the result or error
*/
@UiThread
@JvmOverloads
fun createRadarSession(
stripeAccountId: String? = this.stripeAccountId,
callback: ApiResultCallback<RadarSession>
) {
executeAsync(callback) {
stripeRepository.createRadarSession(
ApiRequest.Options(
apiKey = publishableKey,
stripeAccount = stripeAccountId
)
)
}
}

private fun <T : StripeModel> executeAsync(
callback: ApiResultCallback<T>,
apiMethod: suspend () -> T?
Expand Down
26 changes: 26 additions & 0 deletions stripe/src/main/java/com/stripe/android/StripeKtx.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.PersonTokenParams
import com.stripe.android.model.PiiTokenParams
import com.stripe.android.model.RadarSession
import com.stripe.android.model.SetupIntent
import com.stripe.android.model.Source
import com.stripe.android.model.SourceParams
Expand Down Expand Up @@ -422,6 +423,31 @@ suspend fun Stripe.createFile(
)
}

/**
* Create a Radar Session.
*
* @throws AuthenticationException failure to properly authenticate yourself (check your key)
* @throws InvalidRequestException your request has invalid parameters
* @throws APIConnectionException failure to connect to Stripe's API
* @throws APIException any other type of problem (for instance, a temporary issue with Stripe's servers)
*/
@Throws(
AuthenticationException::class,
InvalidRequestException::class,
APIConnectionException::class,
APIException::class
)
suspend fun Stripe.createRadarSession(): RadarSession {
return runApiRequest {
stripeRepository.createRadarSession(
ApiRequest.Options(
apiKey = publishableKey,
stripeAccount = stripeAccountId
)
)
}
}

/**
* Retrieve a [PaymentIntent] from a coroutine.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.stripe.android.model

import kotlinx.parcelize.Parcelize

@Parcelize
data class RadarSession(
val id: String
) : StripeModel
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.stripe.android.model.parsers

import com.stripe.android.model.RadarSession
import com.stripe.android.model.StripeJsonUtils
import org.json.JSONObject

internal class RadarSessionJsonParser : ModelJsonParser<RadarSession> {
override fun parse(json: JSONObject): RadarSession? {
return StripeJsonUtils.optString(json, FIELD_ID)?.let {
RadarSession(it)
}
}

private companion object {
private const val FIELD_ID = "id"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ internal enum class AnalyticsEvent(internal val code: String) {
AuthSourceRedirect("auth_source_redirect"),
AuthSourceResult("auth_source_result"),

RadarSessionCreate("radar_session_create"),

CardMetadataPublishableKeyAvailable("card_metadata_pk_available"),
CardMetadataPublishableKeyUnavailable("card_metadata_pk_unavailable"),

Expand Down
Loading