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

Introduce suspend fun for Stripe Android SDK - part2 #3568

Merged
merged 7 commits into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions stripe/api/stripe.api
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,8 @@ public final class com/stripe/android/Stripe {
public static synthetic fun handleNextActionForSetupIntent$default (Lcom/stripe/android/Stripe;Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V
public static synthetic fun handleNextActionForSetupIntent$default (Lcom/stripe/android/Stripe;Landroidx/fragment/app/Fragment;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V
public final fun isAuthenticateSourceResult (ILandroid/content/Intent;)Z
public final fun isPaymentResult (ILandroid/content/Intent;)Z
public final fun isSetupResult (ILandroid/content/Intent;)Z
public final fun onAuthenticateSourceResult (Landroid/content/Intent;Lcom/stripe/android/ApiResultCallback;)V
public final fun onPaymentResult (ILandroid/content/Intent;Lcom/stripe/android/ApiResultCallback;)Z
public final fun onSetupResult (ILandroid/content/Intent;Lcom/stripe/android/ApiResultCallback;)Z
Expand Down Expand Up @@ -932,6 +934,9 @@ public final class com/stripe/android/StripeKtxKt {
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 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;
public static final fun getPaymentIntentResult (Lcom/stripe/android/Stripe;ILandroid/content/Intent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun getSetupIntentResult (Lcom/stripe/android/Stripe;ILandroid/content/Intent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun retrievePaymentIntent (Lcom/stripe/android/Stripe;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun retrievePaymentIntent$default (Lcom/stripe/android/Stripe;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static final fun retrieveSetupIntent (Lcom/stripe/android/Stripe;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down
80 changes: 60 additions & 20 deletions stripe/src/main/java/com/stripe/android/PaymentController.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.stripe.android

import android.content.Intent
import com.stripe.android.exception.APIConnectionException
import com.stripe.android.exception.APIException
import com.stripe.android.exception.AuthenticationException
import com.stripe.android.exception.InvalidRequestException
import com.stripe.android.model.ConfirmPaymentIntentParams
import com.stripe.android.model.ConfirmStripeIntentParams
import com.stripe.android.model.Source
Expand Down Expand Up @@ -39,49 +43,85 @@ internal interface PaymentController {
)

/**
* Decide whether [handlePaymentResult] should be called.
* Decide whether [getPaymentIntentResult] should be called.
*/
fun shouldHandlePaymentResult(requestCode: Int, data: Intent?): Boolean

/**
* Decide whether [handleSetupResult] should be called.
* Decide whether [getSetupIntentResult] should be called.
*/
fun shouldHandleSetupResult(requestCode: Int, data: Intent?): Boolean

/**
* Decide whether [getAuthenticateSourceResult] should be called.
*/
fun shouldHandleSourceResult(requestCode: Int, data: Intent?): Boolean

/**
* If payment authentication triggered an exception, get the exception object and pass to
* [ApiResultCallback.onError].
*
* Otherwise, get the PaymentIntent's client_secret from {@param data} and use to retrieve
* Get the PaymentIntent's client_secret from [data] and use to retrieve
* the PaymentIntent object with updated status.
*
* @param data the result Intent
* @return the [PaymentIntentResult] object
*
* @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 IllegalArgumentException if the PaymentIntent response's JsonParser returns null
*/
fun handlePaymentResult(
data: Intent,
callback: ApiResultCallback<PaymentIntentResult>
@Throws(
AuthenticationException::class,
InvalidRequestException::class,
APIConnectionException::class,
APIException::class,
IllegalArgumentException::class
)
suspend fun getPaymentIntentResult(data: Intent): PaymentIntentResult

/**
* If setup authentication triggered an exception, get the exception object and pass to
* [ApiResultCallback.onError].
*
* Otherwise, get the SetupIntent's client_secret from {@param data} and use to retrieve the
* SetupIntent object with updated status.
* Get the SetupIntent's client_secret from [data] and use to retrieve
* the SetupIntent object with updated status.
*
* @param data the result Intent
* @return the [SetupIntentResult] object
*
* @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 IllegalArgumentException if the SetupIntent response's JsonParser returns null
*/
fun handleSetupResult(
data: Intent,
callback: ApiResultCallback<SetupIntentResult>
@Throws(
AuthenticationException::class,
InvalidRequestException::class,
APIConnectionException::class,
APIException::class,
IllegalArgumentException::class
)
suspend fun getSetupIntentResult(data: Intent): SetupIntentResult

fun handleSourceResult(
data: Intent,
callback: ApiResultCallback<Source>
/**
* Get the Source's client_secret from [data] and use to retrieve
* the Source object with updated status.
*
* @param data the result Intent
* @return the [Source] object
*
* @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 IllegalArgumentException if the Source response's JsonParser returns null
*/
@Throws(
AuthenticationException::class,
InvalidRequestException::class,
APIConnectionException::class,
APIException::class,
IllegalArgumentException::class
)
suspend fun getAuthenticateSourceResult(data: Intent): Source

/**
* Determine which authentication mechanism should be used, or bypass authentication
Expand Down
53 changes: 44 additions & 9 deletions stripe/src/main/java/com/stripe/android/Stripe.kt
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,20 @@ class Stripe internal constructor(
)
}

/**
* Check if the requestCode and [Intent] is for [PaymentIntentResult].
* The [Intent] should be retrieved from the result from `Activity#onActivityResult(int, int, Intent)}}`
* by [Activity] started with [confirmPayment] or [handleNextActionForPayment].
*
* @return whether the requestCode and intent is for [PaymentIntentResult].
*/
fun isPaymentResult(
requestCode: Int,
data: Intent?
): Boolean {
return data != null && paymentController.shouldHandlePaymentResult(requestCode, data)
}

/**
* Should be called via `Activity#onActivityResult(int, int, Intent)}}` to handle the
* result of a PaymentIntent automatic confirmation (see [confirmPayment]) or
Expand All @@ -333,8 +347,10 @@ class Stripe internal constructor(
data: Intent?,
callback: ApiResultCallback<PaymentIntentResult>
): Boolean {
return if (data != null && paymentController.shouldHandlePaymentResult(requestCode, data)) {
paymentController.handlePaymentResult(data, callback)
return if (data != null && isPaymentResult(requestCode, data)) {
executeAsync(callback) {
paymentController.getPaymentIntentResult(data)
}
true
} else {
false
Expand Down Expand Up @@ -597,6 +613,20 @@ class Stripe internal constructor(
)
}

/**
* Check if the requestCode and [Intent] is for [SetupIntentResult].
* The [Intent] should be retrieved from the result from `Activity#onActivityResult(int, int, Intent)}}`
* by [Activity] started with [confirmSetupIntent].
*
* @return whether the requestCode and intent is for [SetupIntentResult].
*/
fun isSetupResult(
requestCode: Int,
data: Intent?
): Boolean {
return data != null && paymentController.shouldHandleSetupResult(requestCode, data)
}

/**
* Should be called via `Activity#onActivityResult(int, int, Intent)}}` to handle the
* result of a SetupIntent confirmation (see [confirmSetupIntent]).
Expand All @@ -607,8 +637,10 @@ class Stripe internal constructor(
data: Intent?,
callback: ApiResultCallback<SetupIntentResult>
): Boolean {
return if (data != null && paymentController.shouldHandleSetupResult(requestCode, data)) {
paymentController.handleSetupResult(data, callback)
return if (data != null && isSetupResult(requestCode, data)) {
executeAsync(callback) {
paymentController.getSetupIntentResult(data)
}
true
} else {
false
Expand Down Expand Up @@ -850,7 +882,11 @@ class Stripe internal constructor(
}

/**
* Should be called in `onActivityResult()` to determine if the result is for Source authentication
* Check if the requestCode and [Intent] is for [Source] authentication.
* The [Intent] should be retrieved from the result from `Activity#onActivityResult(int, int, Intent)}}`
* by [Activity] started with [authenticateSource].
*
* @return whether the requestCode and intent is for [Source] authentication
*/
fun isAuthenticateSourceResult(
requestCode: Int,
Expand All @@ -868,10 +904,9 @@ class Stripe internal constructor(
data: Intent,
callback: ApiResultCallback<Source>
) {
paymentController.handleSourceResult(
data,
callback
)
executeAsync(callback) {
paymentController.getAuthenticateSourceResult(data)
}
}

/**
Expand Down
120 changes: 116 additions & 4 deletions stripe/src/main/java/com/stripe/android/StripeKtx.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.stripe.android

import android.content.Intent
import androidx.annotation.Size
import com.stripe.android.exception.APIConnectionException
import com.stripe.android.exception.APIException
Expand Down Expand Up @@ -570,11 +571,122 @@ suspend fun Stripe.confirmPaymentIntent(
*
* @return the result if the API result and JSON parsing are successful; otherwise, throw an exception.
*/
private inline fun <reified APIObject : StripeModel> runApiRequest(
block: () -> APIObject?
): APIObject =
private inline fun <reified ApiObject : StripeModel> runApiRequest(
block: () -> ApiObject?
): ApiObject =
runCatching {
requireNotNull(block()) {
"Failed to parse ${APIObject::class.java.simpleName}."
"Failed to parse ${ApiObject::class.java.simpleName}."
}
}.getOrElse { throw StripeException.create(it) }

/**
* Get the [PaymentIntentResult] from [Intent] returned via
* Activity#onActivityResult(int, int, Intent)}} for PaymentIntent automatic confirmation
* (see [confirmPayment]) or manual confirmation (see [handleNextActionForPayment]})
*
* @param requestCode [Int] code passed from Activity#onActivityResult
* @param data [Intent] intent from Activity#onActivityResult
*
* @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.getPaymentIntentResult(
requestCode: Int,
data: Intent,
): PaymentIntentResult {
return runApiRequest(
isPaymentResult(
requestCode,
data
)
) { paymentController.getPaymentIntentResult(data) }
}

/**
* Get the [SetupIntentResult] from [Intent] returned via
* Activity#onActivityResult(int, int, Intent)}} for SetupIntentResult confirmation.
* (see [confirmSetupIntent])
*
* @param requestCode [Int] code passed from Activity#onActivityResult
* @param data [Intent] intent from Activity#onActivityResult
*
* @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,
IllegalArgumentException::class
)
suspend fun Stripe.getSetupIntentResult(
requestCode: Int,
data: Intent,
): SetupIntentResult {
return runApiRequest(
isSetupResult(
requestCode,
data
)
) { paymentController.getSetupIntentResult(data) }
}

/**
* Get the [Source] from [Intent] returned via
* Activity#onActivityResult(int, int, Intent)}} for [Source] authentication.
* (see [authenticateSource])
*
* @param requestCode [Int] code passed from Activity#onActivityResult
* @param data [Intent] intent from Activity#onActivityResult
*
* @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.getAuthenticateSourceResult(
requestCode: Int,
data: Intent,
): Source {
return runApiRequest(
isAuthenticateSourceResult(
requestCode,
data
)
) { paymentController.getAuthenticateSourceResult(data) }
}

/**
* Consume the [IllegalArgumentException] caused by empty result from Stripe's internal Json Parser,
* throw [InvalidRequestException] for public API.
*
* @return the result if the API result and JSON parsing are successful; otherwise, throw an exception.
*/
internal inline fun <reified ApiObject : StripeModel> runApiRequest(
isValidParam: Boolean,
block: () -> ApiObject
): ApiObject =
runCatching {
require(isValidParam) {
"Incorrect requestCode and data for ${ApiObject::class.java.simpleName}."
}
block()
}.getOrElse { throw StripeException.create(it) }
Loading