Implementation Guide
Before starting implementation you should have completed installation of the MTP SDK.
This guide includes all generic steps and steps specific for MTP SDK with Google Play Services support. To configure application for HMS supported MTP SDK, refer to Configure for HMS.
Quick Steps
MeaTokenPlatform
is the main class for interaction with MTP SDK and MeaCard
is the main interface for interaction with a digitized card.
Card Digitization
Use the following steps to implement minimum required functionality for card digitization:
- Initialize with
MeaTokenPlatform.initialize(...)
method - Register with
MeaTokenPlatform.register(...)
method - Initialize card digitization with
MeaTokenPlatform.initializeDigitization(...)
method - Complete card digitization with
MeaTokenPlatform.completeDigitization(...)
method - Forward remote push messages with
MeaTokenPlatform.Rns.onMessageReceived(...)
method - Listen for card state change events with
MeaTokenPlatform.setDigitizedCardStateChangeListener(MeaDigitizedCardStateChangeListener)
- Listen for card payment tokens replenish events with
MeaTokenPlatform.setCardReplenishListener(MeaCardReplenishListener)
Payments
Use the following steps to implement minimum required functionality for contactless payments:
- Set default application for contactless payments with
MeaTokenPlatform.setDefaultPaymentApplication(...)
method - Select card for transaction with
meaCard.selectForContactlessPayment(...)
method - Listen for transaction events with
MeaTokenPlatform.registerTransactionReceiver(Context, MeaTransactionReceiver)
- Listen for requested authentication events with
MeaTokenPlatform.setAuthenticationListener(MeaAuthenticationListener)
- Set user authenticated with
MeaTokenPlatform.authenticateWithDeviceUnlock()
method
Initialization
MTP SDK should be initialized before you can use it in your mobile app. Initialization should be called only once during app runtime. Library can be initialized with 2 methods:
- synchronous, where the initialization is done on main thread:
MeaTokenPlatform.initialize(Context)
- asynchronous, where the initialization is done on a background thread:
MeaTokenPlatform.initialize(Context, MeaListener)
Usually MTP SDK is initialized in Application.onCreate()
of the Application
subclass:
public class IssuerApplication extends Application {
@Override
public void onCreate() {
try {
MeaTokenPlatform.initialize(this);
} catch (InitializationFailedException ex) {
MeaErrorCode errorCode = ex.getErrorCode();
...
}
if (MeaTokenPlatform.isRegistered()) {
MeaTokenPlatform.registerDeviceUnlockReceiver();
MeaTokenPlatform.registerTransactionReceiver(this, new TransactionReceiver());
...
}
}
}
Alternatively you can initialize Issuer Pay library asynchronously before enabling Tap & Pay functionality in your application:
if (!MeaTokenPlatform.isInitialized()) {
MeaTokenPlatform.initialize(getApplication(), new MeaListener() {
@Override
public void onSuccess() {
...
// Enable 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 checks whether or not the device is a payment network approved device, hardware and software compatibility and any applicable Issuer policies related to the device.
Issuer app should be enabled for Firebase Cloud Messaging (FCM): Set up a Firebase Cloud Messaging client app on Android.
After setting up FCM, to get Firebase device token use FirebaseInstanceId.getInstance().getToken()
and pass the value as pushServiceInstanceIdToken
to MeaTokenPlatform.register(...)
method.
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;
...
}
...
}
});
Initialize Card Digitization
MeaTokenPlatform.initializeDigitization(...)
initializes the card digitization process. During digitization process WSP verifies eligibility of the card and returns eligibility receipt. Eligibility receipt value is required to complete card digitization.
There are 6 card digitization types:
Card ID / Secret
MeaInitializeDigitizationParameters.withCardSecret(...)
MeaInitializeDigitizationParameters digitizationParametersWithCardSecret =
MeaInitializeDigitizationParameters.withCardSecret(cardId, cardSecret);
MeaInitializeDigitizationParameters digitizationParametersWithEncryptedPan =
MeaInitializeDigitizationParameters.withEncryptedPan(encryptedCardData, publicKeyFingerprint, encryptedKey, initialVector);
See the manual for Card Data Encryption to generate encryptedCardData
.
End-to-end encryption implicitly applied for VISA PANs.
MeaInitializeDigitizationParameters digitizationParametersWithPan =
MeaInitializeDigitizationParameters.withPan(pan, month, year, cardHolderName);
MeaInitializeDigitizationParameters digitizationParametersWithReceipt =
MeaInitializeDigitizationParameters.withReceipt(receipt, bin);
MeaInitializeDigitizationParameters digitizationParametersWithIntent =
MeaInitializeDigitizationParameters.withIntent(intent);
E2E Encrypted Data
MeaInitializeDigitizationParameters.withE2eEncryptedData(...)
Constract E2eEncryptedDigitizationParameters
object from plain card data. Use it to initiate digitisation with E2E Encrypted Data.
E2eEncryptedDigitizationParameters e2eEncryptedParameters =
new E2eEncryptedDigitizationParameters(paymentNetwork, bin, pan, cardHolderName, month, year);
MeaInitializeDigitizationParameters digitizationParametersWithE2eEncryptedData =
MeaInitializeDigitizationParameters.withE2eEncryptedData(e2eEncryptedParameters);
Example:
MeaTokenPlatform.initializeDigitization(digitizationParametersWithCardSecret,
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 one of the callback methods is called (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) sends a push notification and application should pass this notification to MTP SDK 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);
}
}
...
}
}
Android 8.0 (API level 26) introduced new background processes optimizations. Due to these optimizations, 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 the code is still executing inside the callback.
Update Device Info
When device information changes, for example, Firebase token is refreshed, wallet application has to use MeaTokenPlatform.updateDeviceInfo(...)
method to send the updated device information.
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) {
...
}
});
}
...
}
}
Card Provision Events
MeaTokenPlatform.setCardProvisionListener(...)
method registers listener to catch card provision events.
When card digitization is successful backend 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();
...
}
});
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 PROVISIONED
, PROVISION_FAILED
, ACTIVE
, SUSPENDED
, DEACTIVATED
or MARKED_FOR_DELETION
.
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, MTP SDK automatically calls meaCard.replenishPaymentTokens()
, except when wallet is configured to use Wallet or Card PIN. In this 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 SDK communicates with backend, and backend delivers new payment tokens to card. Replenishment is done through the network it can take some time.
Once replenishment is completed, onReplenishCompleted(...)
event of 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 when the number of tokens is below configured threshold (by default 5). When MTP SDK returns MeaErrorCode.CARD_NO_PAYMENT_TOKENS
error code, wallet application can use meaCard.replenishPaymentTokens()
method to request payment token replenishment.
Default Application for Contactless Payments
MTP SDK uses Host-based Card Emulation Service (HCE) to emulate a card and communicate with NFC reader. Wallet application has to make sure that MTP SDK host-based emulation service (HCE) is set as the Tap & Pay default service in 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 user 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 default NFC settings outside of the application.
if (MeaTokenPlatform.isDefaultPaymentApplication()) {
...
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
...
MeaTokenPlatform.setDefaultPaymentApplication(...);
...
}
Payment Application Name and Banner 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
.
Image dimensions for Android 12 and lower should be 260 x 96 dp
, and a square icon for Android 13 or later. Ideally, it should be identical to the application launcher icon design.
For more details on customizing HCE service appearance in Android settings, see the corresponding section on Host-based Card Emulation Service (HCE).
Contactless Payments with Locked Device
If app is set as default application for contactless payments, and if there is a default card selected, user can start a contactless payment with a locked device, however the screen must be awake. When contactless payment is started with locked device, user needs to unlock phone to complete an NFC transaction.
Google introduced a security setting "Require device unlock for NFC" in Android 12. When "Require device unlock for NFC" setting is turned on, contactless payments can be started only with an unlocked phone.
Users can disable this setting on Android 12 or later:
- Go to Settings.
- Connected devices > Connection preferences > NFC.
- Turn off "Require device unlock for NFC".
Automating secure NFC check
App developer can automate secure NFC check using MTP SDK provided utility methods:
MeaTokenPlatform.isSecureNfcSupported()
To find out if particular device has secure NFC option.MeaTokenPlatform.isSecureNfcEnabled()
Verify if secure NFC is turned on.MeaTokenPlatform.openSecureNfcSettings(activity)
Open Settings section where secure NFC option can be turned off.
if (MeaTokenPlatform.isSecureNfcSupported() && MeaTokenPlatform.isSecureNfcEnabled()) {
MeaTokenPlatform.openSecureNfcSettings(activity);
}
Select Card for Transaction
For contactless payments Issuer app has to select a card with meaCard.selectForContactlessPayment(...)
method. After completed transaction the card is deselected automatically by the library, to deselect manually use meaCard.deselectForContactlessPayment()
method.
When library instance is killed, card selection is lost. To ensure there is always a card selected for contactless payments, even when the application is not running, app can set default card for contactless payments with
meaCard.setAsDefaultForContactlessPayments()
method. If both selected card and default card are set, then selected card has a higher priority and is used for payments.
It is the responsibility of Issuer app to explicitly control the selection state as part of the application lifecycle.
If there are no payment tokens left for card and meaCard.selectForContactlessPayment(...)
method is called, MTP SDK throws MeaErrorCode.CARD_NO_PAYMENT_TOKENS
error code. When this error code is returned application should use meaCard.replenishPaymentTokens()
method to request payment token replenishment.
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;
...
}
...
}
});
Transaction Events
When Android device interacts with POS terminal, MTP SDK triggers events to inform the Issuer app about the start and the outcome of the contactless transaction. Transaction events are sent using Android broadcast messages. For security and efficiency reasons MTP SDK is using specifically Android Local Broadcasts.
Transaction broadcast receiver implementation has to be registered with MeaTokenPlatform.registerTransactionReceiver(Context, MeaTransactionReceiver)
method in Application
class onCreate()
method.
public class MyApplication extends Application {
@Override
public void onCreate() {
// MTP-SDK initialization
...
MeaTokenPlatform.registerTransactionReceiver(this, new TransactionReceiver());
}
}
MTP SDK contains abstract MeaTransactionReceiver
class, which should be extended and overriden to simplify intent data handling.
handleOnTransactionStartedIntent(...)
event is triggered when first command from the POS terminal has been received by MTP SDK. Handling of this event is optional, but highly recommended to improve user experience. It allows to start opening Activity, showing transaction progress and status before transaction outcome status is received. It is recommended to make this Activity as lightweight as possible to reduce opening time.If user authentication is required for a payment transaction,
handleOnAuthenticationRequiredIntent(...)
event is triggered, which asks the Issuer application to authenticate user and let the user to tap again.When transaction is successfully submitted to the POS terminal
handleOnTransactionSubmittedIntent(...)
event is triggered.In case of transaction failure
handleOnTransactionFailureIntent(...)
event is triggered.
Broadcast events allow the application to handle transaction events even when app is not in foreground.
!!! attention
Be aware of Restrictions on starting activities from the background introduced in Android 10 (API level 29). In most cases Android OS allows 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);
}
}
Cardholder Verification
If MTP SDK is configured to use ALWAYS_CDCVM
or FLEXIBLE_CDCVM
(Consumer Device Cardholder Verification Method) model, then the Issuer application is responsible to inform MTP SDK 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, face ID or fingerprint. Swipe is not considered as legitimate authentication method.
For more information read:
Ahead-of-Time Authentication (Single Tap)
For a transaction to succeed with a single tap, Issuer app should ensure that user is authenticated before a transaction is attempted.
Request ahead of time authentication:
MeaTokenPlatform.requestCardholderAuthentication();
After invoking MeaTokenPlatform.requestCardholderAuthentication()
MTP SDK determines the correct MeaAuthenticationListener
callback method to trigger, depending on SDK and card profile configuration.
Requested Authentication Events
To receive authentication events triggered by MeaTokenPlatform.requestCardholderAuthentication()
method, you need to implement MeaAuthenticationListener
in your application and pass it into MTP SDK using MeaTokenPlatform.setAuthenticationListener(...)
method.
Below is an example how to implement and handle onDeviceUnlockRequired(...)
event using Android KeyguardManager
confirm device credential intent.
If Issuer wallet uses device unlock as user authentification, make sure app invokes MeaTokenPlatform.registerDeviceUnlockReceiver()
.
public class IssuerApplication extends Application {
@Override
public void onCreate() {
try {
MeaTokenPlatform.initialize(this);
}
catch (InitializationFailedException ex) {
MeaErrorCode errorCode = ex.getErrorCode();
...
}
if (MeaTokenPlatform.isRegistered()) {
MeaTokenPlatform.registerDeviceUnlockReceiver();
MeaTokenPlatform.registerTransactionReceiver(this, new TransactionReceiver());
...
}
}
}
public class CardActivity extends AppCompatActivity implements MeaAuthenticationListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
MeaTokenPlatform.setAuthenticationListener(this);
...
}
@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 opens device unlock screen.
// And then pending intent opens 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;
...
}
...
}
}
Set User Authenticated
After wallet application has authenticated user, it has to inform MTP SDK using one of the following method:
- In case of device unlock authentication use
MeaTokenPlatform.authenticateWithDeviceUnlock()
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 does the technical approval of a transaction i.e. checking correctness of the transaction details and that cryptograms are correct.
As the result app receives the transaction outcome push message, which can be parsed using MeaTokenPlatform.Rns.parseTransactionMessage(...)
method into MeaTransactionMessage
object. See Forward Remote Push Messages code sample.
Delete Cards
Delete
Network has to be available when invoking these methods:
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 is retried next time when card deletion job runs.
Mark for Deletion
Network is not required to invoke these methods:
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 MTP SDK has stored in local storage, including application instance id, cards and payment tokens.
After invoking this method a new Registration can be done.
Get Asset (Optional)
MeaTokenPlatform.getAsset(...)
gets static Assets from token service (e.g. MDES, VTS) 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 does not change.
MeaTokenPlatform.getAsset(assetId, new MeaGetAssetListener() {
@Override
public void onSuccess(MeaMediaContent[] assetMediaContents) {
...
}
@Override
public void onFailure (MeaError error) {
...
}
});
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 provisions the card in an Inactive state and returns 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;
...
}
...
}
});
}
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
MTP SDK returns 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)
MTP SDK does not perform any currency conversions, it only checks if transaction amount value does not exceed allowed limit amount value.
Errors
Not Initialized Error
All MeaTokenPlatform
and MeaCard
methods (except initialize methods) throw NotInitializedException
or in case of listener returns MeaErrorCode.NOT_INITIALIZED
.
You should implement MTP SDK 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 MTP SDK gracefully, so that user can resume using application.
Other Common Errors
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_...
MTP SDK storage is automatically deleted when following errors happens.
ROOTED_...
FINGERPRINT_CHANGED
STORAGE_CORRUPTED_ATTACK_DETECTED
STORAGE_MIGRATION_FAILED
By choice application can handle FINGERPRINT_CHANGED
, STORAGE_CORRUPTED_ATTACK_DETECTED
, STORAGE_MIGRATION_FAILED
errors and call MeaTokenPlatform.initialize(...)
again.