Skip to content

Commit

Permalink
Merge pull request #2006 from Adyen/feature/card-scanning-setup-scanner
Browse files Browse the repository at this point in the history
[Card scanning] Setup scanner
  • Loading branch information
OscarSpruit authored Feb 19, 2025
2 parents 1a8abc0 + eb48b02 commit 77cf6b8
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 2 deletions.
32 changes: 32 additions & 0 deletions card-scanning/api/card-scanning.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
public final class com/adyen/checkout/card/scanning/AdyenCardScanner {
public fun <init> ()V
public final fun getResult (Landroid/content/Intent;)Lcom/adyen/checkout/card/scanning/AdyenCardScannerResult;
public final fun initialize (Landroid/content/Context;Lcom/adyen/checkout/core/Environment;)V
public final fun isAvailable (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun startScanner (Landroid/app/Activity;I)V
public final fun terminate ()V
}

public final class com/adyen/checkout/card/scanning/AdyenCardScannerResult {
public fun <init> (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/Integer;
public final fun component3 ()Ljava/lang/Integer;
public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;)Lcom/adyen/checkout/card/scanning/AdyenCardScannerResult;
public static synthetic fun copy$default (Lcom/adyen/checkout/card/scanning/AdyenCardScannerResult;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/adyen/checkout/card/scanning/AdyenCardScannerResult;
public fun equals (Ljava/lang/Object;)Z
public final fun getExpiryMonth ()Ljava/lang/Integer;
public final fun getExpiryYear ()Ljava/lang/Integer;
public final fun getPan ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class com/adyen/checkout/card/scanning/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field CHECKOUT_VERSION Ljava/lang/String;
public static final field DEBUG Z
public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
public fun <init> ()V
}

6 changes: 5 additions & 1 deletion card-scanning/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ android {
}
}

dependencies {}
dependencies {
api project(':checkout-core')

implementation(libs.google.pay.play.services.wallet)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,107 @@

package com.adyen.checkout.card.scanning

class AdyenCardScanner
import android.app.Activity
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.IntentSender
import androidx.core.app.ActivityCompat
import com.adyen.checkout.core.AdyenLogLevel
import com.adyen.checkout.core.Environment
import com.adyen.checkout.core.exception.CheckoutException
import com.adyen.checkout.core.internal.util.adyenLog
import com.google.android.gms.wallet.PaymentCardRecognitionIntentRequest
import com.google.android.gms.wallet.PaymentCardRecognitionResult
import com.google.android.gms.wallet.PaymentsClient
import com.google.android.gms.wallet.Wallet
import com.google.android.gms.wallet.WalletConstants
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume

class AdyenCardScanner {

private var paymentsClient: PaymentsClient? = null
private var isAvailable = false
private var paymentCardRecognitionPendingIntent: PendingIntent? = null

fun initialize(context: Context, environment: Environment) {
val googleEnv = if (environment == Environment.TEST) {
WalletConstants.ENVIRONMENT_TEST
} else {
WalletConstants.ENVIRONMENT_PRODUCTION
}

val walletOptions = Wallet.WalletOptions.Builder()
.setEnvironment(googleEnv)
.build()

paymentsClient = Wallet.getPaymentsClient(context, walletOptions)
}

suspend fun isAvailable(): Boolean {
val paymentsClient = paymentsClient ?: error("initialize must be called before checking availability")

return suspendCancellableCoroutine { continuation ->
val request = PaymentCardRecognitionIntentRequest.getDefaultInstance()
paymentsClient
.getPaymentCardRecognitionIntent(request)
.addOnSuccessListener { intentResponse ->
if (continuation.isActive) {
paymentCardRecognitionPendingIntent = intentResponse.paymentCardRecognitionPendingIntent
continuation.resume(true)
}
}
.addOnFailureListener { e ->
adyenLog(AdyenLogLevel.WARN, e) { "Card scanning not available" }
if (continuation.isActive) {
continuation.resume(false)
}
}
.addOnCanceledListener {
if (continuation.isActive) {
continuation.cancel()
}
}
}
}

fun startScanner(activity: Activity, requestCode: Int) {
val paymentCardRecognitionPendingIntent =
paymentCardRecognitionPendingIntent ?: error("isAvailable must be called before starting the scanner")

try {
ActivityCompat.startIntentSenderForResult(
activity,
paymentCardRecognitionPendingIntent.intentSender,
requestCode,
null,
0,
0,
0,
null,
)
} catch (e: IntentSender.SendIntentException) {
throw CheckoutException("Failed to start payment card recognition.", e)
}
}

fun getResult(data: Intent?): AdyenCardScannerResult? {
data ?: return null
return PaymentCardRecognitionResult.getFromIntent(data)
?.let { result ->
AdyenCardScannerResult(
result.pan,
result.creditCardExpirationDate?.month,
result.creditCardExpirationDate?.year,
)
}
}

fun terminate() {
paymentsClient = null
isAvailable = false
paymentCardRecognitionPendingIntent?.cancel()
paymentCardRecognitionPendingIntent = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by oscars on 13/2/2025.
*/

package com.adyen.checkout.card.scanning

data class AdyenCardScannerResult(
val pan: String?,
val expiryMonth: Int?,
val expiryYear: Int?,
)

0 comments on commit 77cf6b8

Please sign in to comment.