[Huawei Developers] John Wo:
Bank Card Recognition

The bank card recognition service can quickly recognize information such as the bank card number, covering mainstream bank cards such as China UnionPay, American Express, Mastercard, Visa, and JCB around the world. It is widely used in finance and payment scenarios requiring bank card binding to quickly extract bank card information, realizing quick input of bank card information.

Configuring App Information in AppGallery Connect

Before you start developing an app, configure app information in AppGallery Connect.

Registering as a Developer

Before you get started, you must register as a Huawei developer and complete identity verification on HUAWEI Developers. For details refer to Registration and Verification.

Creating an App

Follow the instructions to create an app in Creating an AppGallery Connect Project and Adding an App to the Project.

Enabling the Service

1. Sign in to AppGallery Connect and select My projects.

2. Find your project from the project list and click the app for which you need to enable a service on the project card.

3. Click the Manage APIs tab and toggle the switch for the service to be enabled.

Adding the AppGallery Connect Configuration File

For adding configuration file refer this below link

https://developer.huawei.com/consum...MSCore-Guides/add-appgallery-0000001050038080

Configuring the Maven Repository Address

For adding Maven files refer this below link

https://developer.huawei.com/consum.../HMSCore-Guides/config-maven-0000001050040031

Integrating the Bank Card Recognition Plug-in

The bank card recognition service supports two SDK integration modes: full SDK and base SDK. You can select either one based on your needs.

Mode 1: Full SDK Integration (Recommended)

Combine the bank card recognition model and bank card recognition plug-in into a package.

The following is the sample code for integration in full SDK mode:

dependencies{
// Import the combined package of the bank card recognition plug-in and recognition capability.
implementation 'com.huawei.hms:ml-computer-card-bcr:2.0.3.301'
}

Mode 2: Base SDK Integration

The sample code is as follows:

dependencies{
// Import the bank card recognition plug-in package.
implementation 'com.huawei.hms:ml-computer-card-bcr-plugin:2.0.3.301'
}

Adding the Configuration to the File Header

After integrating the SDK in either mode, add the following information under apply plugin: 'com.android.application' in the file header:

apply plugin: 'com.huawei.agconnect'

Development Process

1. Create the recognition result callback function and reload the onSuccess, onCanceled, onFailure, and onDenied methods.

onSuccess indicates that the recognition is successful.

MLBcrCaptureResult indicates the recognition result.

onCanceled indicates that the user cancels the recognition.

onFailure indicates that the recognition fails.

onDenied indicates that the recognition request is denied, For example the camera is unavailable.

private void initCallBack() {
callback = new MLBcrCapture.Callback() {
@Override
public void onSuccess(MLBcrCaptureResult bankCardResult) {
if (bankCardResult != null) {
String cardNumber = bankCardResult.getNumber();
String cardExpire = bankCardResult.getExpire();
String cardIssuer = bankCardResult.getIssuer();
String cardType = bankCardResult.getType();
String cardOrganization = bankCardResult.getOrganization();
CardModel cardModel = new CardModel(cardNumber, cardExpire, cardIssuer, cardType, cardOrganization);
Intent intent = new Intent(ScanCardActivity.this, PaymentActivity.class);
intent.putExtra("CardData", cardModel);
startActivity(intent);
}
// Processing for successful recognition.
}

@Override
public void onCanceled() {
// Processing for recognition request cancelation.
}

// Callback method used when no text is recognized or a system exception occurs during recognition.
// retCode: result code.
// bitmap: bank card image that fails to be recognized.
@Override
public void onFailure(int retCode, Bitmap bitmap) {
// Processing logic for recognition failure.
}

@Override
public void onDenied() {
// Processing for recognition request deny scenarios, for example, the camera is unavailable.
}
};
}

2. Set the recognition parameters for calling the captureFrame API of the recognizer. The recognition result is returned through the callback function created in initCallBack method.

private void startCaptureActivity(MLBcrCapture.Callback callback) {
MLBcrCaptureConfig config = new MLBcrCaptureConfig.Factory()
// Set the expected result type of bank card recognition.
// MLBcrCaptureConfig.RESULT_NUM_ONLY: Recognize only the bank card number.
// MLBcrCaptureConfig.RESULT_SIMPLE: Recognize only the bank card number and validity period.
// MLBcrCaptureConfig.ALL_RESULT: Recognize information such as the bank card number, validity period, issuing bank, card organization, and card type.
.setResultType(MLBcrCaptureConfig.RESULT_ALL)
// Set the recognition screen display orientation.
// MLBcrCaptureConfig.ORIENTATION_AUTO: adaptive mode. The display orientation is determined by the physical sensor.
// MLBcrCaptureConfig.ORIENTATION_LANDSCAPE: landscape mode.
// MLBcrCaptureConfig.ORIENTATION_PORTRAIT: portrait mode.
.setOrientation(MLBcrCaptureConfig.ORIENTATION_AUTO)
.create();
MLBcrCapture bankCapture = MLBcrCaptureFactory.getInstance().getBcrCapture(config);
bankCapture.captureFrame(this, callback);
}

3. In the callback of the recognition button, call the method defined in startCaptureActivity method to implement bank card recognition

@Override
public void onClick(View view) {
switch (view.getId()) {
// Detection button.
case R.id.btn_scan_bank_card:
if (checkPermissions())
startCaptureActivity(callback);
break;
default:
break;
}
}

Adding Permissions

CAMERA: To use the camera on the device for recognition or detection, your app needs to apply for the camera permission.

READ_EXTERNAL_STORAGE: To use general card recognition plug-in, your app needs to apply for the file read permission.

The procedure is as follows:

1. Specify permissions in the AndroidManifest.xml file.

<!--Camera permission-->
<uses-permission android:name="android.permission.CAMERA" />
<!--Read permission-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

2. After specifying the permissions in the AndroidManifest.xml file, dynamically apply for the permissions in the code for Android 6.0 and later versions.

private int PERMISSION_REQUEST_CODE = 10;
private boolean checkPermissions() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
// The app has the camera permission.
return true;
} else {
// Apply for the camera permission.
requestCameraPermission();
}
return false;
}

private void requestCameraPermission() {
final String[] permissions = new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE};
ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE);
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0) {
boolean cameraAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
boolean storageAccepted = grantResults[1] == PackageManager.PERMISSION_GRANTED;
if (cameraAccepted && storageAccepted ) {
Toast.makeText(ScanCardActivity.this, "Permission Granted, Now you can access camera", Toast.LENGTH_SHORT).show();
startCaptureActivity(callback);
} else {
requestCameraPermission();
}
}
}
}

Find the activity_scan_card.xml as follows

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000"
android:orientation="vertical">

<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_scan_bank_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="50dp"
android:background="@drawable/btn_background"
android:text="Scan Bank Card"
android:textAllCaps="false"
android:textColor="#fff" />
</RelativeLayout>

In the onSuccess method we used one CardModel class implemented with Parcelable interface. Here CardModel is used for passing data through intent to PaymentActivity. Check the CardModel class below

public class CardModel implements Parcelable {
private String cardNumber;
private String cardExpire;
private String cardIssuer;
private String cardType;
private String cardOrganization;

public CardModel(String cardNumber, String cardExpire, String cardIssuer, String cardType,String cardOrganization) {
this.cardNumber = cardNumber;
this.cardExpire = cardExpire;
this.cardIssuer = cardIssuer;
this.cardType = cardType;
this.cardOrganization = cardOrganization;
}

protected CardModel(Parcel in) {
cardNumber = in.readString();
cardExpire = in.readString();
cardIssuer = in.readString();
cardType = in.readString();
cardOrganization = in.readString();
}

public static final Creator<CardModel> CREATOR = new Creator<CardModel>() {
@Override
public CardModel createFromParcel(Parcel in) {
return new CardModel(in);
}

@Override
public CardModel[] newArray(int size) {
return new CardModel[size];
}
};

public String getCardNumber() {
return cardNumber;
}

public String getCardExpire() {
return cardExpire;
}

public String getCardIssuer() {
return cardIssuer;
}

public String getCardType() {
return cardType;
}

public String getCardOrganization() {
return cardOrganization;
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(cardNumber);
parcel.writeString(cardExpire);
parcel.writeString(cardIssuer);
parcel.writeString(cardType);
parcel.writeString(cardOrganization);
}
}

After reconizing card details we are passing all the details to PaymentActivity for further steps. Check the activity_payment.xml below

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000">

<RelativeLayout
android:id="@+id/rl_tool"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginBottom="10dp">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Payment"
android:textColor="#fff"
android:textSize="16sp"
android:textStyle="bold" />
</RelativeLayout>

<androidx.cardview.widget.CardView
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_below="@+id/rl_tool"
android:layout_margin="10dp"
app:cardCornerRadius="10dp">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">

<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/chip" />

<TextView
android:id="@+id/txt_user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="YOUR NAME"
android:textColor="#fff"
android:textStyle="bold" />

<TextView
android:id="@+id/txt_card_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/txt_valid"
android:layout_centerHorizontal="true"
android:shadowColor="#7F000000"
android:shadowDx="1"
android:shadowDy="2"
android:shadowRadius="5"
android:textColor="#FBFBFB"
android:textSize="24sp"
android:textStyle="bold" />

<TextView
android:id="@+id/txt_valid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/img_organization"
android:layout_centerHorizontal="true"
android:text="VALID\nTHRU"
android:textColor="#fff"
android:textSize="10sp" />

<TextView
android:id="@+id/txt_expiry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/txt_card_number"
android:layout_centerHorizontal="true"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:layout_toEndOf="@id/txt_valid"
android:textColor="#fff"
android:textSize="16sp"
android:textStyle="bold" />

<TextView
android:id="@+id/txt_card_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:textAllCaps="true"
android:textColor="#fff"
android:textSize="18sp"
android:textStyle="bold" />

<ImageView
android:id="@+id/img_organization"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true" />
</RelativeLayout>
</androidx.cardview.widget.CardView>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/card"
android:orientation="vertical">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Verify your Card information"
android:textColor="#fff"
android:textSize="14sp" />

<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textColorHint="#fff"
app:hintTextColor="#fff">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edt_card_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Card Number"
android:textColor="#fff" />
</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textColorHint="#fff"
app:hintTextColor="#fff">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edt_valid_thru"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Valid Thru"
android:textColor="#fff" />
</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textColorHint="#fff"
app:hintTextColor="#fff">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edt_cvv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="CVV"
android:inputType="textPassword"
android:text="***"
android:textColor="#fff" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

<androidx.appcompat.widget.AppCompatButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_margin="20dp"
android:background="@drawable/btn_background"
android:paddingStart="20dp"
android:paddingTop="5dp"
android:paddingEnd="20dp"
android:paddingBottom="5dp"
android:text="Pay Now"
android:textColor="#fff"
android:textSize="12sp" />
</RelativeLayout>

In the PaymentActivity, We differentiated cards based on getCardOrganization method. Added different background colors and related data. Check the PaymentActivity code below

public class PaymentActivity extends AppCompatActivity {
private TextView txtCardNumber;
private TextView txtCardType;
private TextView txtExpire;
private TextView txtUserName;
private ImageView imgOrganization;
private CardView cardBackground;
private TextInputEditText edtCardNumber;
private TextInputEditText edtValid;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_payment);
init();
Intent intent = getIntent();
if (intent != null) {
CardModel cardModel = intent.getParcelableExtra("CardData");
if (cardModel != null) {
setCardData(cardModel);
}
}
}

private void setCardData(CardModel cardModel) {
String cardNumber = cardModel.getCardNumber().replaceAll("....", "$0 ");
txtCardNumber.setText(cardNumber);
edtCardNumber.setText(cardNumber);
txtExpire.setText(cardModel.getCardExpire());
edtValid.setText(cardModel.getCardExpire());
if (cardModel.getCardType() == null) {
txtCardType.setText("CARD TYPE");
} else {
txtCardType.setText(cardModel.getCardType());
}
String cardOrganization = cardModel.getCardOrganization();
if (cardOrganization != null) {
if (cardOrganization.equalsIgnoreCase("MASTERCARD")) {
setCardBackgroundAndOrganization(R.drawable.master_card, "#c22e67");
} else if (cardOrganization.equalsIgnoreCase("VISA")) {
setCardBackgroundAndOrganization(R.drawable.visa, "#4812e8");
} else if (cardOrganization.equalsIgnoreCase("UnionPay")) {
imgOrganization.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.union));
cardBackground.setCardBackgroundColor(Color.parseColor("#918B8B"));
Shader shader = new LinearGradient(70, 50, 100, 100, Color.RED, Color.BLACK, Shader.TileMode.CLAMP);
txtCardType.getPaint().setShader(shader);
}
} else {
txtCardNumber.setTextColor(Color.BLACK);
txtExpire.setTextColor(Color.BLACK);
txtCardType.setTextColor(Color.BLACK);
txtUserName.setTextColor(Color.BLACK);
}
Toast.makeText(this, cardModel.getCardOrganization() + " " + cardModel.getCardIssuer() + " " + cardModel.getCardType(), Toast.LENGTH_LONG).show();
}

private void init() {
txtCardNumber = findViewById(R.id.txt_card_number);
txtCardType = findViewById(R.id.txt_card_type);
txtExpire = findViewById(R.id.txt_expiry);
imgOrganization = findViewById(R.id.img_organization);
cardBackground = findViewById(R.id.card);
edtCardNumber = findViewById(R.id.edt_card_number);
edtValid = findViewById(R.id.edt_valid_thru);
txtUserName = findViewById(R.id.txt_user_name);
}

private void setCardBackgroundAndOrganization(int cardOrganization, String backgroundColor) {
imgOrganization.setImageDrawable(ContextCompat.getDrawable(this, cardOrganization));
cardBackground.setCardBackgroundColor(Color.parseColor(backgroundColor));
Shader shader = new LinearGradient(0, 0, 0, 100, Color.WHITE, Color.DKGRAY, Shader.TileMode.CLAMP);
txtCardType.getPaint().setShader(shader);
}
}

Find the output in below images

Tips and Tricks

We need to use CAMERA and READ_EXTERNAL_STORAGE permissions. It is recognizing cardNumber, cardType, cardIssuer and cardOrganization but it is not recognizing cardHolderName. For some of the cards it is returning same value and some cards null values for cardType, cardIssuer and cardOrganization

Conclusion

In this article we can learn about how to scan and get bank card details by using ML Kit Bank card recognition and also populating the recognized data to payment screen with good User Interface.

Reference links

https://developer.huawei.com/consum...Guides/bank-card-recognition-0000001050038118