openplanning

Định dạng số thẻ tín dụng với Android TextWatcher

Xem thêm các chuyên mục:

Nhóm phát triển của chúng tôi vừa ra mắt website langlearning.net học tiếng Anh, Nga, Đức, Pháp, Việt, Trung, Hàn, Nhật, ... miễn phí cho tất cả mọi người.
Là một website được viết trên công nghệ web Flutter vì vậy hỗ trợ rất tốt cho người học, kể cả những người học khó tính nhất.
Hiện tại website đang tiếp tục được cập nhập nội dung cho phong phú và đầy đủ hơn. Mong các bạn nghé thăm và ủng hộ website mới của chúng tôi.
Hãy theo dõi chúng tôi trên Fanpage để nhận được thông báo mỗi khi có bài viết mới. Facebook

1- Credit Card Number Pattern

Card Type
The issuing network
Length
Number of allowable digits
IIN Ranges
The card number will always begin with ...
Spacing Patterns
How the digits and spaces are printed on the physical credit cards
Visa (incl. VPay) 13-19 4 #### #### #### ####
(4-4-4-4)
Pattern not known for 13-15 and 17-19 digit cards.
Visa Electron 16 4026, 417500, 4405, 4508, 4844, 4913, 4917 #### #### #### ####
(4-4-4-4)
American Express 15 34, 37 #### ###### #####
(4-6-5)
MasterCard 16 51‑55,
222100‑272099
#### #### #### ####
(4-4-4-4)
Diners Club Carte Blanche 14 300‑305 #### ###### ####
(4-6-4)
Diners Club International 14 300‑305,
309, 36,
38‑39
#### ###### ####
(4-6-4)
Diners Club United States & Canada 16 54, 55 #### #### #### ####
(4-4-4-4)
Discover 16 6011,
622126‑622925,
644‑649,
65
#### #### #### ####
(4-4-4-4)
JCB 16 3528‑3589 #### #### #### ####
(4-4-4-4)
UATP 15 1 #### ##### ######
(4-5-6)
Dankort 16 5019 #### #### #### ####
(4-4-4-4)
InterPayment 16-19 636 #### #### #### ####
(4-4-4-4)
Pattern not known for 17-19 digit cards.
Maestro 12-19 500000‑509999,
560000‑589999,
600000‑699999
#### #### ##### (4-4-5)
#### ###### ##### (4-6-5)
#### #### #### #### (4-4-4-4)
#### #### #### #### ### (4-4-4-4-3)
Pattern not known for 12, 14, 17, and 18 digit cards.
China UnionPay 16-19 62 #### #### #### #### (4-4-4-4)
###### ############# (6-13)
Pattern not known for 17-18 digit cards.

2- Example: TextWatcher Credit Card Number

Trong ví dụ này, Lớp CreditCardNumberTextWatcher sẽ dựa trên các ký tự đầu tiên mà người dùng nhập vào EditText để phát hiện ra kiểu Credit Card:
Tiếp đó nó dựa trên kiểu Credit Card để định dạng số thẻ mà người dùng đang nhập vào:
Visa Electron Credit Card Type. (####-####-####-####) (4-4-4-4)
UATP Credit Card Type. (####-#####-######) (4-5-6)
OK, Trên Android Studio tạo mới một Project:
  • File > New > New Project > Empty Activity
    • Name: TextWatcherCreditCardExample
    • Package name: org.o7planning.textwatchercreditcardexample
    • Language: Java
Copy một vài file ảnh vào thư mục drawable của project:
CreditCardType.java

package org.o7planning.textwatchercreditcardexample;

import java.util.Arrays;

public enum CreditCardType {
    // 4
    VISA("Visa", "icon_visa",
            13, 19,
            new int[]{4},
            new int[]{4,4,4,4,4}
    ),
    // 4026, 417500, 4405, 4508, 4844, 4913, 4917
    VISA_ELECTRON("Visa Electron", "icon_visa_electron",
            16, 16,
            new int[]{4026, 417500, 4405, 4508, 4844, 4913, 4917},
            new int[]{4,4,4,4}
    ),
    // 34, 37
    AMERICAN_EXPRESS("American Express", "icon_american_express",
            15, 15,
            new int[]{34, 37},
            new int[]{4,6,5}
    ),
    // 51‑55, 222100‑272099
    MASTERCARD("MasterCard", "icon_mastercard",
            16, 16,
            concat(intRange(51,55), intRange(222100,272099)),
            new int[]{4,4,4,4}
    ),
    // 6011, 622126‑622925, 644‑649, 65
    DISCOVER("MasterCard", "icon_mastercard",
            16, 16,
            append(concat(intRange(622126,622925), intRange(644,649)),6011,65),
            new int[]{4,4,4,4}
    ),
    // 3528‑3589
    JCB("JCB", "icon_jcb",
            16, 16,
             intRange(3528,3589),
            new int[]{4,4,4,4}
    ),
    // 1
    UATP("UATP", "icon_uatp",
            15, 15,
            new int[]{1},
            new int[]{4,5,6}
    ),
    // 5019
    DANKORT("Dankort", "icon_dankort",
            16, 16,
            new int[]{5019},
            new int[]{4,4,4,4}
    );

    public static final int[] DEFAULT_BLOCK_LENGTHS = new int[] {4, 4, 4, 4, 4};
    public static final int DEFAULT_MAX_LENGTH = 4 * 5 ;

    private String[] prefixs;
    private int[] blockLengths;
    private String name;
    // Name of Image in "drawable" folder.
    private String imageResourceName;

    private int minLength;
    private int maxLength;

    CreditCardType(String name, String imageResourceName,
                    int minLength, int maxLength,
                    int[] intPrefixs, int[] blockLengths)  {
        this.name = name;
        this.imageResourceName = imageResourceName;
        if(intPrefixs!= null)  {
            this.prefixs = new String[intPrefixs.length];
            for(int i=0;i< intPrefixs.length;i++) {
                this.prefixs[i] = String.valueOf(intPrefixs[i]);
            }
        }
        this.minLength = minLength;
        this.maxLength = maxLength;
        this.blockLengths = blockLengths;
    }

    public String getName() {
        return name;
    }

    public int getMinLength() {
        return minLength;
    }

    public int getMaxLength() {
        return maxLength;
    }
    public String[] getPrefixs()  {
        return this.prefixs;
    }

    public int[] getBlockLengths()  {
        return this.blockLengths;
    }

    public String getImageResourceName()  {
        return this.imageResourceName;
    }

    private static int[] intRange(int from, int to)  {
        int length = to - from + 1;
        int[] ret = new int[length];
        for(int i= from; i < to + 1; i++) {
            ret[i-from] = i;
        }
        return ret;
    }

    private static int[] concat(int[] first, int[] second)  {
        int[] both = Arrays.copyOf(first, first.length + second.length);
        System.arraycopy(second, 0, both, first.length, second.length);
        return both;
    }

    private static int[] append(int[] first, int ... value)  {
        if(value == null || value.length == 0)  {
            return first;
        }
        int[] both = Arrays.copyOf(first, first.length + value.length);

        for(int i = 0; i < value.length; i++) {
            both[first.length + i] = value[i];
        }
        return both;
    }

    public static CreditCardType detect(String creditCardNumber)  {
        if(creditCardNumber == null || creditCardNumber.isEmpty())  {
            return null;
        }
        CreditCardType found = null;
        int max = 0;
        for(CreditCardType type: CreditCardType.values()) {
            for(String prefix : type.prefixs) {
                if(creditCardNumber.startsWith(prefix) && prefix.length() > max) {
                    found = type;
                    max = prefix.length();
                }
            }
        }
        return found;
    }

}
CreditCardNumberTextWatcher.java

package org.o7planning.textwatchercreditcardexample;

import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.EditText;

public class CreditCardNumberTextWatcher implements TextWatcher {

    private static final String LOG_TAG = "AndroidExample";
    public static final char SEPARATOR = '-';

    private EditText editText;

    private int after;
    private String beforeString;

    public CreditCardNumberTextWatcher(EditText editText) {
        this.editText = editText;
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        this.after = after;
        this.beforeString = s.toString();
        Log.e(LOG_TAG, "@@beforeTextChanged s=" + s
                + " . start="+ start+" . after=" + after+" . count="+ count);
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        Log.e(LOG_TAG, "@@onTextChanged s=" + s
                + " . start="+ start+" . before=" + before+" . count="+ count);
        String newText = s.toString();

        String textPrefix = newText.substring(0, start);
        String textInserted = newText.substring(start, start + this.after);
        String textSuffix = newText.substring(start + this.after);
        String textBeforeCursor = textPrefix + textInserted;

        // User delete the SEPARATOR.
        if(this.after == 0 && count == 0 && beforeString.charAt(start) == SEPARATOR)  {
            if(start > 0)  {
                textPrefix = textPrefix.substring(0, textPrefix.length() -1);
            }
        }

        // Non-digit
        String regex = "[^\\d]";

        String textPrefixClean = textPrefix.replaceAll(regex, "");
        String textInsertedClean = textInserted.replaceAll(regex, "");
        String textSuffixClean = textSuffix.replaceAll(regex, "");
        String textBeforeCursorClean = textPrefixClean + textInsertedClean;

        // creditCardNumber
        String newTextClean = textPrefixClean + textInsertedClean + textSuffixClean;

        CreditCardType creditCardType = this.showDetectedCreditCardImage(newTextClean);

        int[] blockLengths = CreditCardType.DEFAULT_BLOCK_LENGTHS; // {4,4,4,4,4}
        int minLength = 0;
        int maxLength = CreditCardType.DEFAULT_MAX_LENGTH; // 4*5

        if(creditCardType != null)  {
            blockLengths = creditCardType.getBlockLengths();
            minLength = creditCardType.getMinLength();
            maxLength = creditCardType.getMaxLength();
        }
        Log.i(LOG_TAG, "newTextClean= " + newTextClean);


        int[] separatorIndexs = new int[blockLengths.length];
        for(int i=0; i < separatorIndexs.length; i++) {
            if(i==0)  {
                separatorIndexs[i] = blockLengths[i];
            } else {
                separatorIndexs[i] = blockLengths[i] + separatorIndexs[i-1];
            }
        }
        Log.i(LOG_TAG, "blockLengths= " + this.toString(blockLengths));
        Log.i(LOG_TAG, "separatorIndexs= " +  this.toString(separatorIndexs));

        int cursorPosition = start + this.after - textBeforeCursor.length() + textBeforeCursorClean.length();

        StringBuilder sb = new StringBuilder();
        int separatorCount = 0;
        int cursorPositionDelta = 0;
        int LOOP_MAX = Math.min(newTextClean.length(), maxLength);

        for(int i = 0; i < LOOP_MAX; i++) {
            sb.append(newTextClean.charAt(i));

            if(this.contains(separatorIndexs,i + 1) && i < LOOP_MAX - 1) {
                sb.append(SEPARATOR);
                separatorCount++;
                if(i < cursorPosition) {
                    cursorPositionDelta++;
                }
            }
        }
        cursorPosition= cursorPosition + cursorPositionDelta;

        String textFormatted = sb.toString();
        if(cursorPosition > textFormatted.length()) {
            cursorPosition =  textFormatted.length();
        }

        this.editText.removeTextChangedListener(this);
        this.editText.setText(textFormatted);
        this.editText.addTextChangedListener(this);
        this.editText.setSelection(cursorPosition);
    }

    @Override
    public void afterTextChanged(Editable s) {

    }

    private String toString(int[] array)  {
        StringBuilder sb= new StringBuilder();
        for(int i=0;i< array.length;i++) {
            if(i == 0) {
                sb.append("[").append(array[i]);
            } else {
                sb.append(", ").append(array[i]);
            }
        }
        sb.append("]");
        return sb.toString();
    }

    private boolean contains(int[] values, int value)  {
        for(int i=0;i<values.length;i++) {
            if(values[i] == value) {
                return true;
            }
        }
        return false;
    }


    private CreditCardType showDetectedCreditCardImage(String creditCardNumber)  {
        CreditCardType type = CreditCardType.detect(creditCardNumber);

        if(type != null)  {
            Drawable icon = ResourceUtils.getDrawableByName(this.editText.getContext(), type.getImageResourceName());
            this.editText.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
        } else {
            Drawable icon = ResourceUtils.getDrawableByName(this.editText.getContext(), "icon_none");
            this.editText.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
        }
        return type;
    }
}
ResourceUtils.java

package org.o7planning.textwatchercreditcardexample;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;

public class ResourceUtils {

    public static Drawable getDrawableByName(Context context, String resourceName)  {
        Resources resources = context.getResources();
        final int resourceId = resources.getIdentifier(resourceName, "drawable",
                context.getPackageName());
        return resources.getDrawable(resourceId);
    }
}
Giao diện của ví dụ:
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView81"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginRight="16dp"
        android:text="Enter Your Credit Card Number:"
        android:textSize="20sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/editText_creditCardNumber"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:drawableRight="@drawable/icon_none"
        android:ems="10"
        android:inputType="phone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView81" />

</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java

package org.o7planning.textwatchercreditcardexample;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.TextWatcher;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {

    private EditText editTextCreditCardNumber;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        this.editTextCreditCardNumber = (EditText) this.findViewById(R.id.editText_creditCardNumber);

        TextWatcher textWatcher = new CreditCardNumberTextWatcher(this.editTextCreditCardNumber);
        this.editTextCreditCardNumber.addTextChangedListener(textWatcher);
    }
}

Xem thêm các chuyên mục: