Debit and credit card recognition

The Google payment card recognition API provides the ability to use a camera to recognize information from payment cards. The API supports recognition of the primary account number (PAN) and the expiration date from a credit card or a debit card through optical character recognition (OCR). The API delegates the task of scanning the card to Google Play services. Therefore, your app doesn't need to request camera permission and only receives the scan results. All image processing occurs on the device and Google doesn't store the results or share the image data.

To ensure the optimal user experience and functionality, the API has the following constraints:

  • The device has a Google account logged in.
  • The device has at least 1 GB of RAM.
  • The device has a back facing camera.
  • The device has the latest Google Play services version.
  • The device supports PORTRAIT orientation.

Create a request

Create a PaymentsClient instance in the onCreate method in your Activity. You can use PaymentsClient to interact with the Google Pay API.

Kotlin

    fun createPaymentsClient(activity: Activity): PaymentsClient {
        val walletOptions = Wallet.WalletOptions.Builder()
                .setEnvironment(Constants.PAYMENTS_ENVIRONMENT)
                .build()

        return Wallet.getPaymentsClient(activity, walletOptions)
    }

Java

  public static PaymentsClient createPaymentsClient(Activity activity) {
    Wallet.WalletOptions walletOptions =
        new Wallet.WalletOptions.Builder().setEnvironment(Constants.PAYMENTS_ENVIRONMENT).build();
    return Wallet.getPaymentsClient(activity, walletOptions);
  }

After you create the response, you can then send an asynchronous request for a PendingIntent, which you can use to start the payment card recognition activity.

Keep in mind that the request doesn't always succeed. If there is no API enablement, the request fails. We suggest that you adjust your app's behavior according to the response to the request. In the sample app, we display the button only after we receive a successful response.

Kotlin

    private fun possiblyShowPaymentCardOcrButton() {
        // The request can be used to configure the type of the payment card recognition. Currently
        // the only supported type is card OCR, so it is sufficient to call the getDefaultInstance()
        // method.
        val request = PaymentCardRecognitionIntentRequest.getDefaultInstance()
        paymentsClient
            .getPaymentCardRecognitionIntent(request)
            .addOnSuccessListener { intentResponse ->
                cardRecognitionPendingIntent = intentResponse.paymentCardRecognitionPendingIntent
                paymentCardOcrButton.visibility = View.VISIBLE
            }
            .addOnFailureListener { e ->
                // The API is not available either because the feature is not enabled on the device
                // or because your app is not registered.
                Log.e(TAG, "Payment card ocr not available.", e)
            }
    }

Java

  public void possiblyShowPaymentCardOcrButton() {
    // The request can be used to configure the type of the payment card recognition. Currently the
    // only supported type is card OCR, so it is sufficient to call the getDefaultInstance() method.
    PaymentCardRecognitionIntentRequest request =
        PaymentCardRecognitionIntentRequest.getDefaultInstance();
    paymentsClient
        .getPaymentCardRecognitionIntent(request)
        .addOnSuccessListener(intentResponse -> {
          cardRecognitionPendingIntent = intentResponse.getPaymentCardRecognitionPendingIntent();
          paymentCardOcrButton.setVisibility(View.VISIBLE);
        })
        .addOnFailureListener(e -> {
          // The API is not available either because the feature is not enabled on the device
          // or because your app is not registered.
          Log.e(TAG, "Payment card ocr not available.", e);
        });
  }

To start the payment card recognition activity, use the following code sample:

Kotlin

    private fun startPaymentCardOcr() {
        try {
            ActivityCompat.startIntentSenderForResult(
                this@CheckoutActivity,
                cardRecognitionPendingIntent.intentSender,
                PAYMENT_CARD_RECOGNITION_REQUEST_CODE,
                null, 0, 0, 0, null
            )
        } catch (e: SendIntentException) {
            throw RuntimeException("Failed to start payment card recognition.", e)
        }
    }

Java

  public void startPaymentCardOcr(View view) {
    try {
      ActivityCompat.startIntentSenderForResult(
          CheckoutActivity.this, cardRecognitionPendingIntent.getIntentSender(),
          PAYMENT_CARD_RECOGNITION_REQUEST_CODE,
          null, 0, 0, 0, null);
    } catch (SendIntentException e) {
      throw new RuntimeException("Failed to start payment card recognition.", e);
    }
  }

Interpret the result

During the recognition process, our algorithm attempts to recognize the payment card. If it successfully recognizes a result, then the API returns the result as a PaymentCardRecognitionResult. The result always contains a card number. The expiration date might not be present if the algorithm fails to detect one, or if the date shows that the card is past its expiration date. For various reasons, a card might not be recognizable. This usually results when a user cancels a flow and the API returns Activity.RESULT_CANCELLED.

Kotlin

    private fun handlePaymentCardRecognitionSuccess(
        cardRecognitionResult: PaymentCardRecognitionResult
    ) {
        val creditCardExpirationDate = cardRecognitionResult.creditCardExpirationDate
        val expirationDate = creditCardExpirationDate?.let { "%02d/%d".format(it.month, it.year) }
        val cardResultText = "PAN: ${cardRecognitionResult.pan}\nExpiration date: $expirationDate"
        Toast.makeText(this, cardResultText, Toast.LENGTH_LONG).show()
    }

Java

  private void handleCardRecognitionSuccess(PaymentCardRecognitionResult cardResult) {

    String expirationDate = null;
    Locale locale = Locale.getDefault();
    CreditCardExpirationDate cardExpirationDate = cardResult.getCreditCardExpirationDate();
    if(cardExpirationDate != null) {
      expirationDate = String.format(locale,
          "%02d/%d", cardExpirationDate.getMonth(), cardExpirationDate.getYear());
    }

    String cardResultString = String.format(locale,
        "PAN: %s\nExpiration date: %s", cardResult.getPan(), expirationDate);
    Toast.makeText(this, cardResultString, Toast.LENGTH_LONG).show();
  }