diff --git a/CHANGELOG.md b/CHANGELOG.md index da685691232..658777932b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### PaymentSheet ### Identity ### Card scanning +[ADDED] [4592](https://github.com/stripe/stripe-android/pull/4592) Add support for launching card scan from fragments. ## 19.2.0 - 2022-02-14 This release includes several bug fixes and upgrades Kotlin to 1.6. diff --git a/stripecardscan/README.md b/stripecardscan/README.md index 14b5ee7ac33..d3fa0c197f9 100644 --- a/stripecardscan/README.md +++ b/stripecardscan/README.md @@ -15,6 +15,9 @@ This library can be used entirely outside of a Stripe integration and with other Note: Your app does not have to be written in kotlin to integrate this library, but must be able to depend on kotlin functionality. +# Example +See the `stripecardscan-example` directory for an example application that you can try for yourself! + # Integration * In app/build.gradle, add these dependencies: ```gradle @@ -23,7 +26,81 @@ Note: Your app does not have to be written in kotlin to integrate this library, } ``` -# Usage +# Credit Card OCR + +Add `CardScanSheet` in your activity or fragment where you want to invoke the verification flow + +Note: the `create` method must be called in your fragment or activity’s `onCreate` method in order to register the `ActivityResultListener` with your activity. + +a. Initialize `CardScanSheet` with your `publishableKey` and the `id, client secret` +b. When it's time to invoke the verification flow, display the sheet with `CardScanSheet.present()` +c. When the verification flow is finished, the sheet will be dismissed and the `onFinished` block will be called with a `CardScanSheetResult` + +```kotlin +class LaunchActivity : AppCompatActivity { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_launch) + + /** + * Create a [CardScanSheet] instance with [ComponentActivity]. + * + * This API registers an [ActivityResultLauncher] into the + * [ComponentActivity], it must be called before the [ComponentActivity] + * is created (in the onCreate method). + */ + val cardScanSheet = CardScanSheet.create( + from = LaunchActivity.this, + stripePublishableKey = "stripe_key", + ) + + findViewById(R.id.scanCardButton).setOnClickListener { _ -> + cardScanSheet.present( + from = LaunchActivity.this, + ) { cardScanSheetResult -> + when (cardScanSheetResult) { + is CardScanSheet.Completed -> { + /* + * The user scanned a card. The result of the scan can be found + * by querying the stripe card image verification endpoint with + * the CVI_ID, CVI_SECRET, and Stripe secret key. + * + * Details about the card itself are returned in the `scannedCard` + * field of the result. + */ + Log.i(cardScanSheetResult.scannedCard.pan) + } + is CardScanSheet.Canceled -> { + /* + * The scan was canceled. This could be because any of the + * following reasons (returned as the + * [CancellationReason] in the result): + * + * - Closed - the user pressed the X + * - Back - the user pressed the back button + * - UserCannotScan - the user is unable to scan this card + * - CameraPermissionDenied - the user did not grant permissions + */ + Log.i(cardScanSheetResult.reason) + } + is CardScanSheet.Failed -> { + /* + * The scan failed. The error that caused the failure is + * included in the [Throwable] `error` field of the verification + * result. + */ + Log.e(cardScanSheetResult.error.message) + } + } + } + } + } +} +``` + +# Credit Card Verification + 1. Create a `CardImageVerificationIntent` (CIV Intent) on your backend Perform the following tasks on your server: @@ -34,13 +111,13 @@ Note: Your app does not have to be written in kotlin to integrate this library, b. Provide the CIV intent's `id` and the `client_secret` to the SDK -2. Add `CardVerificationSheet` in your activity or fragment where you want to invoke the verification flow +2. Add `CardImageVerificationSheet` in your activity or fragment where you want to invoke the verification flow Note: the `create` method must be called in your fragment or activity’s `onCreate` method in order to register the `ActivityResultListener` with your activity. - a. Initialize `CardVerificationSheet` with your `publishableKey` and the `id, client secret` - b. When it's time to invoke the verification flow, display the sheet with `CardVerificationSheet.present()` - c. When the verification flow is finished, the sheet will be dismissed and the `onFinished` block will be called with a `CardVerificationSheetResult` + a. Initialize `CardImageVerificationSheet` with your `publishableKey` and the `id, client secret` + b. When it's time to invoke the verification flow, display the sheet with `CardImageVerificationSheet.present()` + c. When the verification flow is finished, the sheet will be dismissed and the `onFinished` block will be called with a `CardImageVerificationSheetResult` ```kotlin class LaunchActivity : AppCompatActivity { @@ -50,27 +127,25 @@ Note: Your app does not have to be written in kotlin to integrate this library, setContentView(R.layout.activity_launch) /** - * Create a [CardVerificationSheet] instance with [ComponentActivity]. + * Create a [CardImageVerificationSheet] instance with [ComponentActivity]. * * This API registers an [ActivityResultLauncher] into the * [ComponentActivity], it must be called before the [ComponentActivity] * is created (in the onCreate method). - * - * see https://github.com/stripe/stripe-android/blob/3e92b79190834dc3aab1c2d9ac2dfb7bc343afd2/payments-core/src/main/java/com/stripe/android/payments/paymentlauncher/PaymentLauncher.kt#L52 */ - val cardVerificationSheet = CardVerificationSheet.create( + val cardImageVerificationSheet = CardImageVerificationSheet.create( from = LaunchActivity.this, stripePublishableKey = "stripe_key", ) findViewById(R.id.scanCardButton).setOnClickListener { _ -> - cardVerificationSheet.present( + cardImageVerificationSheet.present( from = LaunchActivity.this, cardImageVerificationIntentId = "civ_id", cardImageVerificationIntentSecret = "civ_client_secret", - ) { cardVerificationSheetResult -> - when (cardVerificationSheetResult) { - is CardVerificationSheetResult.Completed -> { + ) { cardImageVerificationSheetResult -> + when (cardImageVerificationSheetResult) { + is CardImageVerificationSheetResult.Completed -> { /* * The user scanned a card. The result of the scan can be found * by querying the stripe card image verification endpoint with @@ -79,28 +154,28 @@ Note: Your app does not have to be written in kotlin to integrate this library, * Details about the card itself are returned in the `scannedCard` * field of the result. */ - Log.i(cardVerificationSheetResult.scannedCard.pan) + Log.i(cardImageVerificationSheetResult.scannedCard.pan) } - is CardVerificationSheetResult.Canceled -> { + is CardImageVerificationSheetResult.Canceled -> { /* * The scan was canceled. This could be because any of the * following reasons (returned as the - * [CardVerificationSheetCancelationReason] in the result): + * [CancellationReason] in the result): * * - Closed - the user pressed the X * - Back - the user pressed the back button * - UserCannotScan - the user is unable to scan this card * - CameraPermissionDenied - the user did not grant permissions */ - Log.i(cardVerificationSheetResult.reason) + Log.i(cardImageVerificationSheetResult.reason) } - is CardVerificationSheetResult.Failed -> { + is CardImageVerificationSheetResult.Failed -> { /* * The scan failed. The error that caused the failure is * included in the [Throwable] `error` field of the verification * result. */ - Log.e(cardVerificationSheetResult.error.message) + Log.e(cardImageVerificationSheetResult.error.message) } } } @@ -108,6 +183,3 @@ Note: Your app does not have to be written in kotlin to integrate this library, } } ``` - -# Example -See the stripecardscan-example directory for an example application that you can try for yourself! diff --git a/stripecardscan/api/stripecardscan.api b/stripecardscan/api/stripecardscan.api index b4e80630ca6..911a728f19d 100644 --- a/stripecardscan/api/stripecardscan.api +++ b/stripecardscan/api/stripecardscan.api @@ -33,11 +33,13 @@ public final class com/stripe/android/stripecardscan/cardimageverification/CardI public static final field Companion Lcom/stripe/android/stripecardscan/cardimageverification/CardImageVerificationSheet$Companion; public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public static final fun create (Landroidx/activity/ComponentActivity;Ljava/lang/String;)Lcom/stripe/android/stripecardscan/cardimageverification/CardImageVerificationSheet; + public static final fun create (Landroidx/fragment/app/Fragment;Ljava/lang/String;)Lcom/stripe/android/stripecardscan/cardimageverification/CardImageVerificationSheet; public final fun present (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V } public final class com/stripe/android/stripecardscan/cardimageverification/CardImageVerificationSheet$Companion { public final fun create (Landroidx/activity/ComponentActivity;Ljava/lang/String;)Lcom/stripe/android/stripecardscan/cardimageverification/CardImageVerificationSheet; + public final fun create (Landroidx/fragment/app/Fragment;Ljava/lang/String;)Lcom/stripe/android/stripecardscan/cardimageverification/CardImageVerificationSheet; } public abstract interface class com/stripe/android/stripecardscan/cardimageverification/CardImageVerificationSheetResult : android/os/Parcelable { @@ -150,11 +152,13 @@ public final class com/stripe/android/stripecardscan/cardscan/CardScanSheet { public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun attachCardScanFragment (Landroidx/lifecycle/LifecycleOwner;Landroidx/fragment/app/FragmentManager;ILkotlin/jvm/functions/Function1;)V public static final fun create (Landroidx/activity/ComponentActivity;Ljava/lang/String;)Lcom/stripe/android/stripecardscan/cardscan/CardScanSheet; + public static final fun create (Landroidx/fragment/app/Fragment;Ljava/lang/String;)Lcom/stripe/android/stripecardscan/cardscan/CardScanSheet; public final fun present (Lkotlin/jvm/functions/Function1;)V } public final class com/stripe/android/stripecardscan/cardscan/CardScanSheet$Companion { public final fun create (Landroidx/activity/ComponentActivity;Ljava/lang/String;)Lcom/stripe/android/stripecardscan/cardscan/CardScanSheet; + public final fun create (Landroidx/fragment/app/Fragment;Ljava/lang/String;)Lcom/stripe/android/stripecardscan/cardscan/CardScanSheet; public final fun removeCardScanFragment (Landroidx/fragment/app/FragmentManager;)V } diff --git a/stripecardscan/src/main/java/com/stripe/android/stripecardscan/cardimageverification/CardImageVerificationSheet.kt b/stripecardscan/src/main/java/com/stripe/android/stripecardscan/cardimageverification/CardImageVerificationSheet.kt index a16d80b18ee..53f5f59e14f 100644 --- a/stripecardscan/src/main/java/com/stripe/android/stripecardscan/cardimageverification/CardImageVerificationSheet.kt +++ b/stripecardscan/src/main/java/com/stripe/android/stripecardscan/cardimageverification/CardImageVerificationSheet.kt @@ -6,6 +6,7 @@ import android.os.Parcelable import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContract +import androidx.fragment.app.Fragment import com.stripe.android.stripecardscan.cardimageverification.exception.UnknownScanException import com.stripe.android.stripecardscan.payment.card.ScannedCard import com.stripe.android.stripecardscan.scanui.CancellationReason @@ -47,29 +48,23 @@ class CardImageVerificationSheet private constructor(private val stripePublishab * This API registers an [ActivityResultLauncher] into the * [ComponentActivity], it must be called before the [ComponentActivity] * is created (in the onCreate method). - * - * see https://github.com/stripe/stripe-android/blob/3e92b79190834dc3aab1c2d9ac2dfb7bc343afd2/payments-core/src/main/java/com/stripe/android/payments/paymentlauncher/PaymentLauncher.kt#L52 */ @JvmStatic fun create(from: ComponentActivity, stripePublishableKey: String) = CardImageVerificationSheet(stripePublishableKey).apply { - launcher = from.registerForActivityResult( - object : ActivityResultContract< - CardImageVerificationSheetParams, - CardImageVerificationSheetResult - >() { - override fun createIntent( - context: Context, - input: CardImageVerificationSheetParams, - ) = this@Companion.createIntent(context, input) - - override fun parseResult( - resultCode: Int, - intent: Intent?, - ) = this@Companion.parseResult(requireNotNull(intent)) - }, - ::onResult, - ) + launcher = from.registerForActivityResult(activityResultContract, ::onResult) + } + + /** + * Create a [CardImageVerificationSheet] instance with [Fragment]. + * + * This API registers an [ActivityResultLauncher] into the [Fragment], it must be called + * before the [Fragment] is created (in the onCreate method). + */ + @JvmStatic + fun create(from: Fragment, stripePublishableKey: String) = + CardImageVerificationSheet(stripePublishableKey).apply { + launcher = from.registerForActivityResult(activityResultContract, ::onResult) } private fun createIntent(context: Context, input: CardImageVerificationSheetParams) = @@ -81,6 +76,21 @@ class CardImageVerificationSheet private constructor(private val stripePublishab ?: CardImageVerificationSheetResult.Failed( UnknownScanException("No data in the result intent") ) + + private val activityResultContract = object : ActivityResultContract< + CardImageVerificationSheetParams, + CardImageVerificationSheetResult + >() { + override fun createIntent( + context: Context, + input: CardImageVerificationSheetParams, + ) = this@Companion.createIntent(context, input) + + override fun parseResult( + resultCode: Int, + intent: Intent?, + ) = this@Companion.parseResult(requireNotNull(intent)) + } } /** diff --git a/stripecardscan/src/main/java/com/stripe/android/stripecardscan/cardscan/CardScanSheet.kt b/stripecardscan/src/main/java/com/stripe/android/stripecardscan/cardscan/CardScanSheet.kt index ee626e69c89..93ad8e23035 100644 --- a/stripecardscan/src/main/java/com/stripe/android/stripecardscan/cardscan/CardScanSheet.kt +++ b/stripecardscan/src/main/java/com/stripe/android/stripecardscan/cardscan/CardScanSheet.kt @@ -8,6 +8,7 @@ import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContract import androidx.annotation.IdRes import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.add import androidx.fragment.app.commit @@ -53,29 +54,23 @@ class CardScanSheet private constructor(private val stripePublishableKey: String * This API registers an [ActivityResultLauncher] into the * [ComponentActivity], it must be called before the [ComponentActivity] * is created (in the onCreate method). - * - * see https://github.com/stripe/stripe-android/blob/3e92b79190834dc3aab1c2d9ac2dfb7bc343afd2/payments-core/src/main/java/com/stripe/android/payments/paymentlauncher/PaymentLauncher.kt#L52 */ @JvmStatic fun create(from: ComponentActivity, stripePublishableKey: String) = CardScanSheet(stripePublishableKey).apply { - launcher = from.registerForActivityResult( - object : ActivityResultContract< - CardScanSheetParams, - CardScanSheetResult - >() { - override fun createIntent( - context: Context, - input: CardScanSheetParams, - ) = this@Companion.createIntent(context, input) - - override fun parseResult( - resultCode: Int, - intent: Intent?, - ) = this@Companion.parseResult(requireNotNull(intent)) - }, - ::onResult, - ) + launcher = from.registerForActivityResult(activityResultContract, ::onResult) + } + + /** + * Create a [CardScanSheet] instance with [Fragment]. + * + * This API registers an [ActivityResultLauncher] into the [Fragment], it must be called + * before the [Fragment] is created (in the onCreate method). + */ + @JvmStatic + fun create(from: Fragment, stripePublishableKey: String) = + CardScanSheet(stripePublishableKey).apply { + launcher = from.registerForActivityResult(activityResultContract, ::onResult) } private fun createIntent(context: Context, input: CardScanSheetParams) = @@ -99,6 +94,21 @@ class CardScanSheet private constructor(private val stripePublishableKey: String } } } + + private val activityResultContract = object : ActivityResultContract< + CardScanSheetParams, + CardScanSheetResult + >() { + override fun createIntent( + context: Context, + input: CardScanSheetParams, + ) = this@Companion.createIntent(context, input) + + override fun parseResult( + resultCode: Int, + intent: Intent?, + ) = this@Companion.parseResult(requireNotNull(intent)) + } } /**