Implementation guide

Before starting implementation you should have finished installation of the Issuer Pay library.

Overview

MeaTokenPlatform is the main class for interaction with the Issuer Pay library and MeaCard is the main interface for interaction with digitized card.

Before you can use the Issuer Pay library in your application, you must initialize it using MeaTokenPlatform.initialize(...) method.

In simplest scenario to get digitized card you only need to invoke following three methods MeaTokenPlatform.register(...)MeaTokenPlatform.initializeDigitization(...) and MeaTokenPlatform.completeDigitization(...).

For the card to be ready for transaction, two more things are required.

After successful digitization the Credential Management System – Dedicated (CMS-D) will send push message to wallet application to provision digitized card and application needs to forward push messages to the Issuer Pay library using MeaTokenPlatform.Rns.onMessageReceived(...) method.

Finally a card requires payment tokens to perform any transaction, the Issuer Pay library will automatically call meaCard.replenishPaymentTokens() after card is provisioned or, if wallet is configured to use Wallet PIN or Card PIN, after MeaTokenPlatform.setWalletPin(...) or meaCard.setPin(...) is completed.

Initialization

Before you can use the Issuer Pay library in your application, you must initialize it. A wallet implementation should initialize the library only once during its lifetime.

The library can be initialized in two ways:

A good place to initialize the Issuer Pay library is in onCreate on an application subclass:

				
					public class MyApplication extends Application {
    @Override
    public void onCreate() {
        try {
            MeaTokenPlatform.initialize(this);
        } catch (InitializationFailedException ex) {
            MeaErrorCode errorCode =  ex.getErrorCode();

            ...
        }

        MeaTokenPlatform.registerTransactionReceiver(this, new TransactionReceiver());

        ...
    }
}				
			

Alternatively you can initialize the Issuer Pay library asynchronous, before enabling/entering Tap&Pay functionality in your application:

				
					if (!MeaTokenPlatform.isInitialized()) {

    MeaTokenPlatform.initialize(getApplication(), new MeaListener() {
            @Override
            public void onSuccess() {

                ...
                // Enable/enter tap & pay functionality

                ...
            }

            @Override
            public void onFailure(MeaError error) {

                ...
                switch (error.getCode()) {

                    case OS_VERSION_NOT_SUPPORTED:

                        ...
                        break;

                    case CARDHOLDER_AUTHENTICATION_NOT_SUPPORTED:

                        ...
                        break;

                    ...
                }

                ...
            }
        });
}				
			

Registration

MeaTokenPlatform.register(...) checks device eligibility and registers application to Wallet Service Provider (WSP). Device eligibility check includes whether or not the device is a Mastercard type-approved device, hardware and software compatibility and any applicable Issuer policies related to the device.

				
					MeaTokenPlatform.register(firebaseInstanceToken, USER_LOCALE, new MeaListener() {

        @Override
        public void onSuccess() {

            ...
        }

        @Override
        public void onFailure(MeaError error){

            ...
            switch (error.getCode()) {

                case ALREADY_REGISTERED:

                    ...
                    break;

                case DEVICE_NOT_ELIGIBLE:

                    ...
                    break;

                case DEVICE_UNLOCK_NOT_ENABLED:

                    ...                
                    break;
                ...
            }

            ...
        }
    });				
			

Instructions how to set up Firebase Cloud Messaging (FCM) in android app.

After setting up FCM, to get Firebase token call FirebaseInstanceId.getInstance().getToken() and pass this value as rnsRegistrationId to MeaTokenPlatform.register(...) method.

Initialize card digitization

MeaTokenPlatform.initializeDigitization(...) initializes the card digitization process. During digitization process the Wallet Service Provider verifies eligibility of the card and returns eligibility receipt. The eligibility receipt value is required to complete digitization.
There are 3 digitization types – MeaInitializeDigitizationParameters.withCardSecret(...)MeaInitializeDigitizationParameters.withPan(...) and MeaInitializeDigitizationParameters.withEncryptedPan(...).

				
					MeaInitializeDigitizationParameters digitizationParametersWithCardSecret =
        MeaInitializeDigitizationParameters.withCardSecret(cardId, cardSecret);

MeaInitializeDigitizationParameters digitizationParametersWithPan =
        MeaInitializeDigitizationParameters.withPan(pan, month, year, cardHolderName);

MeaInitializeDigitizationParameters digitizationParametersWithEncryptedPan =
        MeaInitializeDigitizationParameters.withEncryptedPan(encryptedCardData, publicKeyFingerprint, encryptedKey, initialVector);				
			

Guide for encryptedCardData generation can be found at the How To section.

				
					MeaTokenPlatform.initializeDigitization(digitizationParametersWithPan,
                                        new MeaInitializeDigitizationListener() {

        @Override
        public void onSuccess(MeaEligibilityReceipt eligibilityReceipt,
                               String termsAndConditionsAssetId,
                               Boolean isSecurityCodeApplicable) {

            ...
        }

        @Override
        public void onFailure(MeaError error) {

            ...
            switch (error.getCode()) {

                case INCORRECT_INPUT_DATA:

                    ...
                    break;

                case CARD_NOT_ELIGIBLE:

                    ...
                    break;

                case CARD_ALREADY_DIGITIZED:

                    ...
                    break;

                case CARD_MARKED_FOR_DELETION:

                    ...
                    break;

                case CARD_DEACTIVATED:

                    ...
                    break;

                ...
            }

            ...
        }
    });				
			

Complete card digitization

MeaTokenPlatform.completeDigitization(...) proceeds with the card eligibility checks and digitization. As a response will be executed one of the callback methods (success, require additional authentication or failure).

				
					if (isSecurityCodeApplicable && TextUtils.isEmpty(cvc2)) {

    ...
    return;
}

MeaTokenPlatform.completeDigitization(eligibilityReceiptValue,
                                      termsAndConditionsAssetId,
                                      termsAndConditionsAcceptTimestamp,
                                      cvc2,
                                      new MeaCompleteDigitizationListener() {
        @Override
        public void onSuccess(MeaCard card) {

            ...
        }

        @Override
        public void onRequireAdditionalAuthentication (MeaCard card) {

            MeaAuthenticationMethod[] authenticationMethods = card.getDigitizationAuthenticationMethods();

            ...
        }

        @Override
        public void onFailure(MeaError error) {

            ...
            switch (error.getCode()) {

                case INCORRECT_INPUT_DATA:

                    ...
                    break;

                case CARD_DIGITIZATION_DECLINED:

                    ...
                    break;

                ...
            }

            ...
        }
    });				
			

Forward remote push messages

If card digitization request is successful then the Credential Management System – Dedicated (CMS-D) will send a push notification and application should pass this notification to the MTP library using MeaTokenPlatform.Rns.onMessageReceived(...) method.

				
					public class MyFcmListenerService extends FirebaseMessagingService {

    @Override
    public void onMessageReceived(final RemoteMessage message) {

        Map<String, String> messageData = message.getData();

        ...
        if (MeaTokenPlatform.Rns.isMeaRemoteMessage(messageData)) {

            if (MeaTokenPlatform.Rns.isMeaTransactionMessage(messageData)) {

                MeaTransactionMessage transactionMessage =
                                        MeaTokenPlatform.Rns.parseTransactionMessage(messageData);

                ...
            } else {

                MeaTokenPlatform.Rns.onMessageReceived(messageData);
            }
        }

        ...
    }
}				
			

Remember that Android O introduces new background process optimizations. Due to these optimizations, the FCM callbacks onMessageReceived() and onTokenRefresh() have a guaranteed life cycle limited to 10 seconds (same as a Broadcast Receiver). After the guaranteed period of 10 seconds, Android considers your process eligible for termination, even if your code is still executing inside the callback.

Listen for card provision events

MeaTokenPlatform.setCardProvisionListener(...) method registers listener to catch card provision events.

When card digitization is successful CMS-D sends a notification and card provision event is triggered. This typically allows a wallet to dynamically update the wallet user interface when new cards are provisioned.

				
					MeaTokenPlatform.setCardProvisionListener(new MeaCardProvisionListener() {

    @Override
    public void onCardProvisionCompleted(MeaCard card) {

        String message = "New card successfully provisioned ***-"
                            + card.getTokenInfo().getTokenPanSuffix();

        Toast.makeText(mActivity, message, Toast.LENGTH_LONG).show();

        ...
    }

    @Override
    public void onCardProvisionFailure(MeaCard card, MeaError error) {

        String message = "Provision failed for card ***-"
                         + card.getTokenInfo().getTokenPanSuffix();

        Toast.makeText(mActivity, message, Toast.LENGTH_LONG).show();

        ...
    }
});				
			

Listen for card state change events

When a card is successfully digitized, you can implement and add MeaDigitizedCardStateChangeListener using MeaTokenPlatform.setDigitizedCardStateChangeListener(...) to listen for card state changes. onStateChanged(MeaCard, MeaCardState) callback is triggered when MeaCardState changes to PROVISIONEDPROVISION_FAILEDACTIVESUSPENDEDDEACTIVATED or MARKED_FOR_DELETION.

Listen for card payment tokens replenish events

A card requires payment tokens to perform any transaction. Each time a transaction is performed, a single token is used.

When a card is provisioned successfully, the MTP library will automatically call meaCard.replenishPaymentTokens(). Exception is when wallet is configured to use Wallet or Card PIN, in that case payment token replenishment is initiated automatically after successful Wallet or Card PIN set.

MeaTokenPlatform.setCardReplenishListener(...) method registers listener to catch card replenish events.

When replenish is initiated, the MTP library communicates with the CMS-D and it sends new payment tokens to card, as this action is done through the network it can take some time. Once it is completed, the onReplenishCompleted(...) event of the MeaCardReplenishListener is triggered.

				
					MeaTokenPlatform.setCardReplenishListener(new MeaCardReplenishListener() {

    @Override
    public void onReplenishCompleted(MeaCard meaCard, int numberOfTransactionCredentials) {

        String message = "Card: ***-"
                         + meaCard.getTokenInfo().getTokenPanSuffix()
                         + " is ready for use";

        Toast.makeText(mActivity, message, Toast.LENGTH_LONG).show();

        ...
    }

    @Override
    public void onReplenishFailed(MeaCard meaCard, MeaError error) {

        String message = "Payment tokens replenishment failed for card: ***-"
                         + meaCard.getTokenInfo().getTokenPanSuffix();

        Toast.makeText(mActivity, message, Toast.LENGTH_LONG).show();

        ...
    }
});				
			

Payment tokens are automatically replenished also when the number is below configured threshold (the default is 5). In case the MTP library returns MeaErrorCode.CARD_NO_PAYMENT_TOKENS error code, wallet application can use the meaCard.replenishPaymentTokens() method to request for payment token replenishment.

Set the default application for payment category

The MTP library uses Host-based Card Emulation Service (HCE) to emulate a card and to talk to the NFC reader. Wallet app don’t need to implement this service, it is implemented already by the library.

Wallet application has to make sure that the library HCE service is set as the tap and pay default service in the android device settings.

MeaTokenPlatform.setDefaultPaymentApplication(...) is a helper method which creates CardEmulation.ACTION_CHANGE_DEFAULT intent to show a system dialog that asks the user whether he wants to replace the current default payment application with your application.
Typically, this should happen on an application startup, in case the user has changed their default NFC settings outside of the application.

				
					if (MeaTokenPlatform.isDefaultPaymentApplication()) {

    ...
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {

    ...    
    MeaTokenPlatform.setDefaultPaymentApplication(...);

    ...
}				
			

Customize payment application name and banner displayed in Android Tap & Pay settings

1. Add file src/main/res/values/strings.xml in application source and add the following 3 lines:

				
					<string name="app_name">CUSTOM_APP_NAME</string>
<string name="apduServiceDescription">CUSTOM_APP_NAME</string>
<string name="aidGroupDescription">CUSTOM_APP_NAME</string>				
			

2. Add image src/main/res/drawable/apdu_service_banner.png with dimensions 260 x 96 dp.

Select card for transaction

For the MTP library to know which specific card to use for the transaction, you have to select a card using meaCard.selectForContactlessPayment(...) method. After a transaction the card is deselected automatically by the library, to deselect manually use meaCard.deselectForContactlessPayment() method.

Selected card is not saved anywhere, when the library instance is killed, this information is lost. To ensure that there will always be a card ready to use, even when the application is not running and there is no selected card, you can set default card for contactless payments by meaCard.setAsDefaultForContactlessPayments() method call. If both selected card and default card are set, then selected card has priority and will be used for payments.

It is the wallet’s responsibility to explicitly control the selection state as part of your application lifecycle.

If there are no payment tokens left for card and meaCard.selectForContactlessPayment(...) method is called, the library will try to replenish payment tokens it self and if that also fails MeaErrorCode.CARD_NO_PAYMENT_TOKENS error code is returned. When this error code is returned application should use the meaCard.replenishPaymentTokens() method to request payment token replenishment it self.

				
					card.selectForContactlessPayment(new MeaCardListener() {
    @Override
    public void onSuccess(MeaCard card){

        ...
        // Add contactless transaction listener

        ...
    }

    @Override
    public void onFailure(MeaError error) {

        ...
        switch (error.getCode()) {

            case NFC_NOT_AVAILABLE:

                ...
                break;

            case HCE_NOT_AVAILABLE:

                ...
                break;

            case APPLICATION_NOT_DEFAULT_FOR_CONTACTLESS:

                ...
                break;

            case DEVICE_UNLOCK_NOT_ENABLED:

                ...
                break;

            case DEVICE_UNLOCK_KEY_INVALIDATED:

                ...
                break;

            case CARD_NOT_SUPPORT_CONTACTLESS_PAYMENTS:

                ...
                break;

            case CARD_NO_PAYMENT_TOKENS:

                ...
                break;

            ...
        }

        ...
    }
});				
			

Listen for transaction events

When Android device interacts with POS terminal, the MTP library events are triggered to inform the wallet application about the start and the outcome of the transaction. Transaction events are sent using Android broadcast messages. For security and efficiency reasons the MTP library is using specifically Android Local Broadcasts.

Transaction events broadcast receiver

Your transaction broadcast receiver implementation has to be registered by calling MeaTokenPlatform.registerTransactionReceiver(Context, MeaTransactionReceiver) method in your Application class onCreate method.

				
					public class MyApplication extends Application {
    @Override
    public void onCreate() {

        // MTP-SDK initialization
        ...

        MeaTokenPlatform.registerTransactionReceiver(this, new TransactionReceiver());
    }
}				
			

The MTP library contains abstract MeaTransactionReceiver class, which application developers should extend and override abstract methods to simplify intent data handling.

  • handleOnTransactionStartedIntent(...) event is triggered when first command from the POS terminal has been received by the MTP library, handling of this event is optional, but highly recommended to improve UX. It allows to start opening Activity, showing transaction progress and status before transaction outcome status is received. Therefore transaction itself will look happening faster. It’s recommended to make this Activity as lightweight as possible to reduce it’s opening time.

  • If user authentication is required for a payment transaction, handleOnAuthenticationRequiredIntent(...) event is raised, which means that wallet application should authenticate user and ask to tap again.

  • When transaction is successfully submitted to the POS terminal handleOnTransactionSubmittedIntent(...) event is raised.

  • In case of transaction failure handleOnTransactionFailureIntent(...) event is raised. Bellow in implementation example you can see all possible transaction error codes.

Broadcast events also allows the application to react to the transaction events even when it is not in the foreground.

Attention

Be aware of Restrictions on starting activities from the background introduced in Android 10 (API level 29). In most cases Android OS will allow to launch Activity when one of the MeaTransactionReceiver events are received, but in case when app is in background it is allowed only when HostApduService is active, this service is responsible for payment processing. We strongly recommend to use handleOnTransactionStartedIntent(...) event for Activity opening to minimize risk of not opened Activity error scenario.

Implementation example of MeaTransactionReceiver class.

				
					public class TransactionReceiver extends MeaTransactionReceiver {

    @Override
    public void handleOnTransactionStartedIntent(Context context, String cardId) {
        ...
        if (!isAppInForeground()) {

            startActivityWhenNotInForeground(...);
        }

        ...
    }

    @Override
    public void handleOnTransactionSubmittedIntent(Context context,
                                                   String cardId,
                                                   MeaContactlessTransactionData data) {

        ...
        if (!isAppInForeground()) {

            startActivityWhenNotInForeground(...);
        }

        ...
    }

    @Override
    public void handleOnTransactionFailureIntent(Context context,
                                                 String cardId,
                                                 MeaError error,
                                                 MeaContactlessTransactionData data) {

         ...
        switch (error.getCode()) {

            case TRANSACTION_ABORTED:
            case TRANSACTION_CARD_ERROR:
            case TRANSACTION_TERMINAL_ERROR:
            case TRANSACTION_FAILED:
            case TRANSACTION_AUTHENTICATE_OFFLINE:
            case TRANSACTION_DECLINED_BY_TERMINAL:
            case TRANSACTION_MAGSTRIPE_TERMINAL_V2_ERROR:
            case TRANSACTION_MANAGER_BUSY:
            case TRANSACTION_MISSING_ICC:
            case TRANSACTION_COMMAND_INCOMPATIBLE:
            case TRANSACTION_INSUFFICIENT_POI_AUTHENTICATION:
            case TRANSACTION_UNSUPPORTED_TRANSIT:
            case TRANSACTION_CONDITIONS_NOT_ALLOWED:
            case TRANSACTION_DECLINED_BY_CARD:

                ...
                break;

             case TRANSACTION_DECLINED_DEVICE_SCREEN_IS_OFF:

                ...
                break;

            case TRANSACTION_DECLINED_LIMIT_EXCEEDED:

                ...
                break;

            case CARD_NO_PAYMENT_TOKENS:

                ...
                break;

            case DEVICE_UNLOCK_KEY_INVALIDATED:

                ...
                break;

            case TRANSACTION_CONTEXT_NOT_MATCHING:

                ...
                break;
            ...
        }
        ...
        if (!isAppInForeground()) {

            startActivityWhenNotInForeground(...);
        }

        ...
    }

    @Override
    public void handleOnAuthenticationRequiredIntent(Context context,
                                                     String cardId,
                                                     MeaContactlessTransactionData data) {

        ...
        // Read section "Cardholder verification" in this implementation guide to find out
        // how to authenticate the user.
        MeaTokenPlatform.requestCardholderAuthentication();

        ...

        if (!isAppInForeground()) {

            startActivityWhenNotInForeground(...);
        }

        ...
    }

    private void startActivityWhenNotInForeground(...) {

        // Depending on Android version and/or your requirements show notification 
        // or open activity over lock screen.

        // Open activity over lock screen:
        // Intent activityIntent = new Intent(context, activityClass);
        // activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        // activityIntent.putExtra(INTENT_START_OVER_LOCK_SCREEN, 1);
        // activityIntent.putExtra(...);
        // ...
        // context.startActivity(activityIntent);
    }
}				
			

Transaction events listener (Deprecated)

Be aware that for all transaction events the MTP library always triggers both broadcast event and the transaction listener callback. To avoid showing duplicate notifications for the user, application can use TransactionIdHexString field in received MeaContactlessTransactionData object, it is unique value for each transaction and present also in the failure events.

meaCard.setContactlessTransactionListener(...) method registers listener to catch card contactless transaction events.

				
					meaCard.setContactlessTransactionListener(new MeaContactlessTransactionListener() {

    @Override
    public void onContactlessPaymentStarted(MeaCard card) {
        ...
    }

    @Override
    public void onContactlessPaymentSubmitted(MeaCard card, MeaContactlessTransactionData data) {
        ...
    }

    @Override
    public void onContactlessPaymentFailure(MeaCard card,
                                            MeaError error,
                                            MeaContactlessTransactionData data) {

        ...
    }

    @Override
    public void onAuthenticationRequired(MeaCard card, MeaContactlessTransactionData data) {
        ...

            MeaTokenPlatform.requestCardholderAuthentication();

        ...
    }
}				
			

Cardholder verification

If the MTP library is configured to use ALWAYS_CDCVM or FLEXIBLE_CDCVM (Consumer Device Cardholder Verification Method) model, then the wallet application is responsible for informing the MTP library whether or not it considers the consumer as authenticated for payment transactions.

Authentication is considered to be any action that proves the person holding the device is the legitimate user of the device. Various methods can be used for this authentication, including PIN, password, pattern or fingerprint. Swipe is not considered as legitimate authentication method.

If wallet application is configured to use CARD_LIKE model then the MTP library will not perform checks to ensure on-device user authentication.

For more information read:

Ahead-of-Time Authentication (Single Tap)

For a transaction to succeed with a single tap, the wallet application must ensure that the user is authenticated before a transaction is attempted.

Request ahead of time authentication:

				
					MeaTokenPlatform.requestCardholderAuthentication();				
			

After invoking MeaTokenPlatform.requestCardholderAuthentication() the MTP library will determine the correct MeaAuthenticationListener callback method to trigger, depending on the MTP library and the card profile configuration.

Listen for requested authentication event

To receive authentication events raised by MeaTokenPlatform.requestCardholderAuthentication() method, you need to implement MeaAuthenticationListener in your application and pass it into the MTP library using MeaTokenPlatform.setAuthenticationListener(...) method.

Below is an example of how to implement and handle onDeviceUnlockRequired(...) event using Android KeyguardManager confirm device credential intent. If the wallet uses device unlock as authentification, then make sure your app invokes also MeaTokenPlatform.registerDeviceUnlockReceiver().

				
					public class CardActivity extends AppCompatActivity implements MeaAuthenticationListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ...
        MeaTokenPlatform.setAuthenticationListener(this);

        ...
    }

    @Override
    public void onResume() {
        super.onResume();

        ...
        if (MeaTokenPlatform.isRegistered()) {

            MeaTokenPlatform.registerDeviceUnlockReceiver();
        }

        ...
    }

    @Override
    public void onDeviceUnlockRequired() {
        ...
        KeyguardManager keyguardManager = (KeyguardManager)getApplicationContext()
                                                        .getSystemService(Context.KEYGUARD_SERVICE);

        if (keyguardManager != null && keyguardManager.isKeyguardSecure()) {

            if (keyguardManager.isDeviceLocked()) {

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

                    // Starting from Android Oreo device
                    // can be unlocked using keyguardManage.requestDismissKeyguard()
                    keyguardManager.requestDismissKeyguard(mBaseActivity.getActivity(),
                                                    new KeyguardManager.KeyguardDismissCallback() {
                        @Override
                        public void onDismissError() {
                            super.onDismissError();

                            MeaCard selectedCard = MeaTokenPlatform.getSelectedCardForContactless();

                            if (selectedCard != null) {
                                selectedCard.stopContactlessTransaction();
                                ...
                            }
                        }

                        @Override
                        public void onDismissSucceeded() {
                            super.onDismissSucceeded();
                            ...
                            MeaTokenPlatform.authenticateWithDeviceUnlock();

                            ...
                        }

                        @Override
                        public void onDismissCancelled() {
                            super.onDismissCancelled();

                             MeaCard selectedCard = MeaTokenPlatform.getSelectedCardForContactless();

                            if (selectedCard != null) {
                                selectedCard.stopContactlessTransaction();
                                ...
                            }
                        }
                    });

                } else {

                    // Bellow Android Oreo show notification to user about transaction.
                    // When notification is pressed android system will open device unlock screen.
                    // After which pending intent will open the wallet app.
                    PendingIntent pendingIntent
                                    = PendingIntent.getActivity(this,
                                                                0,
                                                                activityIntent,
                                                                PendingIntent.FLAG_UPDATE_CURRENT);
                    NotificationCompat.Builder notification = new NotificationCompat.Builder(...);
                    NotificationManager notificationManager
                            = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                    notificationManager.notify(id, notification.build())
                }

            } else {
                Intent intent = keyguardManager
                                    .createConfirmDeviceCredentialIntent("Enter credentials",
                                                                    "To authenticate for payment.");
                startActivityForResult(intent, REQUEST_CODE_DEVICE_UNLOCK);
            }
        } else {
            // Inform user that device lock screen is not secure.

            ...
        }

        ...
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        ...
        if (requestCode == REQUEST_CODE_DEVICE_UNLOCK) {

            switch (resultCode) {
                case RESULT_OK:
                    ...
                    MeaTokenPlatform.authenticateWithDeviceUnlock();

                    ...
                break;

                case RESULT_CANCELED:

                    ...
                    MeaCard selectedCard = MeaTokenPlatform.getSelectedCardForContactless();

                    if (selectedCard != null) {
                        selectedCard.stopContactlessTransaction();
                        ...
                    }

                    ...
                break;
        }

        ...
    }

    @Override
    public void onFailure(MeaError error) {

        ...
        switch (error.getCode()) {

            case CARDHOLDER_AUTHENTICATION_NOT_REQUIRED:

                ...
                break;

            case DEVICE_UNLOCK_NOT_ENABLED:

                ...
                break;

            ...
        }

        ...
    }
}				
			

Inform the library that the user is authenticated

After wallet application has authenticated user, it has to inform the MTP library using one of the following method:

Transaction outcome push message

After wallet app receives payment submitted event in its transaction listener or broadcast receiver, the transaction is sent by the POS to the TMS and TMS will do the technical approval of a transaction i.e. checking correctness of the transaction details and that cryptograms are correct.

As the result app will receive the transaction outcome push message, which can be parsed using MeaTokenPlatform.Rns.parseTransactionMessage(...) method into MeaTransactionMessage object. Example how to handle this push notification message in apps Firebase messaging service you can see in this code sample.

Delete cards

Delete

Network has to be available when invoking these methods.

  • meaCard.delete(MeaCardListener)
  • MeaTokenPlatform.deleteAllCards(MeaListener)

Listener success callback is invoked when card is deleted both remotely and from local storage. When delete methods are invoked first they change card state to MeaCardState.MARKED_FOR_DELETION and schedule card deletion job. If remote request fails, then card state stays in MeaCardState.MARKED_FOR_DELETION and deletion will be retried next time when card deletion job runs.

Mark for deletion

Network is not required to invoke these methods.

  • meaCard.markForDeletion(MeaCardListener)
  • MeaTokenPlatform.markAllCardsForDeletion(MeaListener)

Listener success callback is invoked when card state is changed to MeaCardState.MARKED_FOR_DELETION.

When network connection becomes available card deletion job is executed and all cards in state MeaCardState.MARKED_FOR_DELETION is deleted remotely and from the local storage.

Card in state MeaCardState.MARKED_FOR_DELETION cannot be used for transactions.

Delete everything locally

MeaTokenPlatform.delete(MeaListener) method deletes everything that the MTP library have in it’s local storage, including application instance id, cards and payment tokens. After invoking this method new registration can be done for the MTP library.

Not initialized error code/exception

All MeaTokenPlatform and MeaCard methods (except initialize methods) throw NotInitializedException or in case of listener returns MeaErrorCode.NOT_INITIALIZED.

You should implement the MTP library in a way to avoid this error.

Just to be on a safe side in a release version application should have general handling of this error to re-initialize the MTP library gracefully, so that user can resume using application.

Error codes

MeaErrorCode class contains all MTP library errors that can be returned in listeners onFailure(MeaError error) response.

These are general errors that can be returned in most of the listeners.

NOT_INITIALIZED 
NOT_REGISTERED
INCORRECT_INPUT_DATA
DEBUGGER_ATTACHED
ABI_NOT_SUPPORTED
OS_VERSION_NOT_SUPPORTED
STORAGE_OPERATION_FAILED
CRYPTO_ERROR INTERNAL_ERROR
STORAGE_CORRUPTED_ATTACK_DETECTED
VERSION_ROLLBACK
GOOGLE_PLAY_SERVICES_NOT_AVAILABLE
ROOTED_...

The MTP library storage is automatically deleted when following errors happens.

ROOTED_... 
FINGERPRINT_CHANGED
STORAGE_CORRUPTED_ATTACK_DETECTED
STORAGE_MIGRATION_FAILED

By choice, application can handle FINGERPRINT_CHANGEDSTORAGE_CORRUPTED_ATTACK_DETECTEDSTORAGE_MIGRATION_FAILED errors and call MeaTokenPlatform.initialize(...) again.

Update device info

When device information changes, for example, Firebase token is refreshed, wallet application have to use MeaTokenPlatform.updateDeviceInfo(...) method to send information to the Wallet Service Provider.

				
					public class MyFcmListenerService extends FirebaseMessagingService {

    @Override
    public void onNewToken(String newToken) {

        ...

        if (MeaTokenPlatform.isRegistered()) {

            MeaTokenPlatform.updateDeviceInfo(newToken, null, new MeaListener() {
                @Override
                public void onSuccess() {

                        ...
                }

                @Override
                public void onFailure(MeaError meaError) {

                        ...
                }
            });
        }

        ...
    }
}				
			

Transaction limits (Optional)

Wallet application can specify maximum amount for single transaction. Limit can be set either as currency specific limit or as default limit for all currencies. Default limit is used only when currency specific limit is not defined for the transaction currency.

Both currency specific limit and default limit amount int value last two digits are decimal values. 10000 = 100.00 EUR

The MTP library will return MeaErrorCode.TRANSACTION_DECLINED_LIMIT_EXCEEDED, when the transaction amount exceeds transaction limit.

				
					// Specific currency limits
MeaTokenPlatform.addTransactionLimit(MeaTransactionLimit transactionLimit, MeaListener listener)
MeaTokenPlatform.addTransactionLimits(List<MeaTransactionLimit> transactionLimits, MeaListener listener)
MeaTransactionLimit MeaTokenPlatform.getTransactionLimit(Currency currency)
List<MeaTransactionLimit> MeaTokenPlatform.getTransactionLimits()
MeaTokenPlatform.removeTransactionLimit(Currency currency, MeaListener listener)

// Default limit
Integer MeaTokenPlatform.getDefaultTransactionLimit()
MeaTokenPlatform.setDefaultTransactionLimit(int amount, MeaListener listener)
MeaTokenPlatform.removeDefaultTransactionLimit(MeaListener listener)

// Clear specific currency limits and default limit
MeaTokenPlatform.clearAllTransactionLimits(MeaListener listener)				
			

The MTP library won’t perform any currency conversions, it will only check if transaction amount value don’t exceed allowed limit amount value.

Set wallet PIN (Optional)

Applies only to wallets configured for Wallet PIN.

MeaTokenPlatform.setWalletPin(...) sets wallet PIN. A wallet PIN is set once and is then used across all cards.

				
					MeaTokenPlatform.setWalletPinListener(new MeaWalletPinListener() {
        @Override
        public void onWalletPinChangeSuccess() {

            ...
        }

        @Override
        public void onWalletPinChangeFailed(MeaError error) {

            ...
            switch (error.getCode()) {

                case INCORRECT_MOBILE_PIN:

                    ...
                    break;

               ...
            }

            ...
        }

        @Override
        public void onWalletPinSetSuccess() {

            ...
        }

        @Override
        public void onWalletPinSetFailed(MeaError error) {

            ...
            switch (error.getCode()) {

                case INCORRECT_MOBILE_PIN:

                    ...
                    break;

                ...
            }

            ...
        }

        @Override
        public void onWalletPinResetSuccess() {

            ...
        }
});

MeaTokenPlatform.setWalletPin("0000");				
			

Initialize additional authentication for digitization (Optional)

In situations where the Issuer is not sure about users presences during digitization, the Issuer can choose to request additional authentication.

For any digitization requests, which decision is REQUIRE_ADDITIONAL_AUTHENTICATION, Wallet Service Provider will provision the card in an Inactive state and will return a list of available authentication methods.

MeaTokenPlatform.initializeAdditionalAuthenticationForDigitization(...) method requests an ‘Activation Code’ to be sent to authenticate the Card Holder. This can be done for any Authentication Method involving a user-entered Authentication Code.

				
					if (card.getYellowPathState() == MeaCardYellowPathState.REQUIRE_ADDITIONAL_AUTHENTICATION) {

    MeaTokenPlatform.initializeAdditionalAuthenticationForDigitization(cardId,
                                                                       authenticationMethodId,
                                                                       new MeaListener() {
        @Override
        public void onSuccess() {

            ...
        }

        @Override
        public void onFailure(MeaError error) {

            ...
        }
    });
}				
			

Complete additional authentication for digitization (Optional)

MeaTokenPlatform.completeAdditionalAuthenticationForDigitization(...) completes authentication, if Additional Authentication for the card was successfully initialized and digitized card is in MeaCardYellowPathState.AUTHENTICATION_INITIALIZED state. Note that the user is only given a limited number of attempts to enter a correct Authentication Code (typically 3 attempts), after which the Authentication Code becomes invalid.

				
					if (card.getYellowPathState() == MeaCardYellowPathState.AUTHENTICATION_INITIALIZED) {

    MeaTokenPlatform.completeAdditionalAuthenticationForDigitization(cardId,
                                                                     authenticationCode,
                                                                     new MeaCompleteAuthenticationListener() {
            @Override
            public void onSuccess(MeaCard card) {

                ...
            }

            @Override
            public void onFailure(MeaError error) {

                ...
                switch (error.getCode()) {
                    case AUTHENTICATION_CODE_EXPIRED:
                    case AUTHENTICATION_CODE_INCORRECT:
                    case AUTHENTICATION_CODE_INCORRECT_RETRIES_EXCEEDED:
                    case AUTHENTICATION_SESSION_EXPIRED:

                        ...
                        break;

                    ...
                }

                ...
            }
        });
}				
			

Get asset (Optional)

MeaTokenPlatform.getAsset(...) gets static Assets from MDESs repository, such as: Card art, card brand logos, Issuers logos, Terms and Conditions. Every Asset in the repository is referenced using an Asset ID. Once an Asset has been assigned to an Asset ID, the contents of the Asset will not change.

				
					MeaTokenPlatform.getAsset(assetId, new MeaGetAssetListener() {

        @Override
        public void onSuccess(MeaMediaContent[] assetMediaContents) {

            ...
        }

        @Override
        public void onFailure (MeaError error) {

            ...
        }
    });				
			
On this page