React native

Installation

Nexus dependency

React Native library react-native-mtp can be installed from Nexus artifact repository.

Steps:

1. Create .npmrc file:

$ touch .npmrc

2. Set an authentication token in .npmrc file:

//nexus-developer.meawallet.com/content/repositories/mtp-npm-snapshots/:_authToken=SOMEKEY

SOMEKEY is a Base64 encoded string of Nexus username and password separated with a colon – Base64(:).

3. Install latest dependency:

$ npm install --save react-native-mtp --registry https://nexus-developer.meawallet.com/content/repositories/mtp-npm-snapshots/

or

$ yarn add react-native-mtp --registry https://nexus-developer.meawallet.com/content/repositories/mtp-npm-snapshots/

or a specific version (for example,  – 2.0.8)

$ npm install --save react-native-mtp@ --registry https://nexus-developer.meawallet.com/content/repositories/mtp-npm-snapshots/

4. Link react-native-mtp library:

$ react-native link react-native-mtp

5. After that ensure that new RNMtpPackage() is added in MainApplication in getPackages() method:

@Override
protected List getPackages() {
  return Arrays.asList(
      new MainReactPackage(),
      new RNMtpPackage()
  );
}

Gradle configuration

Add Maven repository and credentials for MTP SDK in project android/build.gradle:

				
					repositories {
    maven {
        url 'https://nexus-developer.meawallet.com/content/groups/mtp-<user>-group'

        credentials {
            username '<user>'
            password '<password>'
        }
    }
}				
			

Add dependency to MTP SDK in app android/app/build.gradle:

				
					dependencies {
    implementation 'com.meawallet:mtp-<user>-test:<version>-debug'
}				
			

More: Gradle configuration

NOTE

1. Replace minSdkVersion to the suggested version, if you get the following error:

Manifest merger failed : uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library...

 

2. Upgrade Gradle plugin, if you get the following error:  

Gradle DSL method not found: 'google()'
Possible causes:<ul><li>The project 'android' may be using a version of the Android Gradle plug-in that does not contain the method (e.g. 'testCompile' was added in 1.1.0).
Upgrade plugin to version 3.1.3 and sync project</li><li>The project 'android' may be using a version of Gradle that does not contain the method.
Open Gradle wrapper file</li><li>The build file may be missing a Gradle plugin.
Apply Gradle plugin</li>

 

Example application

$ cd react-native/example

$ npm install

$ npm start

$ npm run android

Implementation guide

Initialization

MTP SDK should be initialized as the first step to be used in the application. Application should initialize the library only once during it’s lifetime. A good place to initialize the MTP library is in onCreate() in the application subclass:

				
					public class MyApplication extends Application {
    ...

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

Alternatively MTP SDK can be initialized asynchronously, before enabling and providing Tap & Pay functionality:

				
					import MeaTokenPlatform from 'react-native-mtp;

initializePlatform = async () => {
    let initialized = await MeaTokenPlatform.isPlatformInitialized();
    if (!initialized) {
        try {
            await MeaTokenPlatform.initializePlatform();
            ...
        } catch (e) {
            let errorCode = e.code;
            ...
        }
    }
}				
			

Registration

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

				
					try {
    await MeaTokenPlatform.register(firebaseInstanceToken, USER_LOCALE)
} catch (e) {
    let errorCode = e.code;
    ...
}				
			

Complete card digitization

MeaTokenPlatform.completeDigitization(...) proceeds with the card eligibility checks and digitization.

				
					import {DeviceEventEmitter} from 'react-native';
import MeaTokenPlatform, {EVENT_TYPE} from 'react-native-mtp';

componentDidMount() {
    this.digitizationSuccessListener = DeviceEventEmitter.addListener(EVENT_TYPE.DigitizationSuccess,(meaCard) => {
        ...
    });

    this.digitizationFailureListener = DeviceEventEmitter.addListener(EVENT_TYPE.DigitizationFailure, (error) => {
        ...
    });

    this.digitizationRequireAdditionalAuthentication = DeviceEventEmitter.addListener(EVENT_TYPE.DigitizationRequireAdditionalAuthentication,
    (meaCard) => {
        ...
    });
}

componentWillUnmount() {
    this.digitizationFailureListener.remove();
    this.digitizationSuccessListener.remove();
    this.digitizationRequireAdditionalAuthentication.remove();
}
...

MeaTokenPlatform.completeDigitization(eligibilityReceiptValue,
                                      termsAndConditionsAssetId,
                                      termsAndConditionsAcceptTimestamp,
                                      cvc2)
...				
			

Remote push message forwarding

See Forward remote push messages for implementation with Java.

Credential Management System – Dedicated (CMS-D) sends a remote push message, if card digitization request is successful. Application should forward this remote push message to MTP SDK using MeaTokenPlatform.onMessageReceived(...) method.

In React Native use react-native-firebase dependency to receive remote push messages from Firebase Cloud Messaging.

				
					import firebase, { RemoteMessage } from 'react-native-firebase'

this.messageListener = firebase.messaging().onMessage( async (message: RemoteMessage) => {
    const messageData = message.data

    if (messageData) {
        try {
            const isMeaRemoteMessage = await MeaTokenPlatform.isMeaRemoteMessage(messageData);

            if (isMeaRemoteMessage) {
                var isMeaTransactionMessage = await MeaTokenPlatform.isMeaTransactionMessage(messageData);

                if (isMeaTransactionMessage) {
                    var transactionMessage = await MeaTokenPlatform.parseTransactionMessage(messageData);
                    ...

                } else {
                    await MeaTokenPlatform.onMessageReceived(messageData);
                }
            }

         } catch (error) {
             console.log(error);
         }
    }
});				
			

Listen for card provision events

MeaTokenPlatform.initCardProvisionListener() method registers listener to catch card provision events. Then use DeviceEventEmitter.addListener(...) to subscribe for provision events.

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

				
					import {DeviceEventEmitter} from 'react-native';
import MeaTokenPlatform, {EVENT_TYPE} from 'react-native-mtp';

componentDidMount() {
    MeaTokenPlatform.initCardProvisionListener();

    this.cardProvisionCompletedListener = DeviceEventEmitter.addListener(EVENT_TYPE.CardProvisionCompleted, (meaCard) => {
        ...
    });

    this.cardProvisionFailureListener = DeviceEventEmitter.addListener(EVENT_TYPE.CardProvisionFailure, (result) => {
        ...
    });
  }

componentWillUnmount() {
    this.cardProvisionCompletedListener.remove();
    this.cardProvisionFailureListener.remove();

    MeaTokenPlatform.removeCardProvisionListener();
}				
			

Listen for card payment tokens replenish events

MeaTokenPlatform.initCardReplenishListener() method registers listener to catch card replenish event. And then use DeviceEventEmitter.addListener(...) to subscribe for replenish events.

				
					import {DeviceEventEmitter} from 'react-native';
import MeaTokenPlatform, {EVENT_TYPE} from 'react-native-mtp';

componentDidMount() {
   MeaTokenPlatform.initCardReplenishListener();

   this.replenishCompletedListener = DeviceEventEmitter.addListener(EVENT_TYPE.ReplenishCompleted, (result) => {
      ...
   });

   this.replenishFailedListener = DeviceEventEmitter.addListener(EVENT_TYPE.RreplenishFailed, (result) => {
      ...
   });
}

componentWillUnmount() {
   this.replenishCompletedListener.remove();
   this.replenishFailedListener.remove();

   MeaTokenPlatform.removeCardReplenishListener();
}				
			

Set the default application for payments

Application must be registered with the Android OS as the designated payment application to handle contactless payments over NFC (as the Host-based Card Emulation Service). Typically, this would happen on an application startup, in case the user has changed their default NFC settings outside of the application.

				
					if (await MeaTokenPlatform.isDefaultPaymentApplication()) {
    ...
} else {
    MeaTokenPlatform.setAsDefaultPaymentApplication();
    ...
}				
			

Select card for contactless transaction

A card should be selected for a contactless transaction using MeaTokenPlatform.selectForContactless(...) method. Use MeaTokenPlatform.deselectForContactless(...) method to deselect the card.

				
					try {
   let card = await MeaTokenPlatform.selectForContactless(cardId);
} catch (e) {
   let errorCode = e.code;

   switch (errorCode) {
     case MeaErrorCode.APPLICATION_NOT_DEFAULT_FOR_CONTACTLESS:
        ...
       break;

     case MeaErrorCode.NFC_NOT_AVAILABLE:
        ...
       break;

     case MeaErrorCode.HCE_NOT_AVAILABLE:
        ...
       break;

     default:
        ...
    }
    ...
}				
			

Listen for contactless transaction events

MeaTokenPlatform.setContactlessTransactionListener(...) method initializes listener to receive card contactless transaction events.

When interaction with terminal completes, events EVENT_TYPE.ContactlessPaymentSubmitted or EVENT_TYPE.ContactlessPaymentFailure are triggered to inform the application about the outcome of contactless transaction.

If user authentication is required for the transaction, event EVENT_TYPE.ContactlessAuthenticationRequired is raised, which requires the application to authenticate the user and ask to tap again.

				
					MeaTokenPlatform.setAsDefaultPaymentApplication();

...

try {
   await MeaTokenPlatform.setContactlessTransactionListener(cardId);
} catch (e) {
   ...
}

DeviceEventEmitter.addListener(EVENT_TYPE.ContactlessPaymentSubmitted, (result) => {
    ...
});

DeviceEventEmitter.addListener(EVENT_TYPE.ContactlessPaymentFailure, (result) => {
    var code = result.meaError.code;

    switch (code) {
        case MeaErrorCode.TRANSACTION_TERMINAL_ERROR:
            ...
            break;

        case MeaErrorCode.TRANSACTION_DECLINED_BY_CARD:
            ...
            break;

        case MeaErrorCode.TRANSACTION_DECLINED_DEVICE_IS_LOCKED:
            ...
            break;

        default:
            ...
    }
});

DeviceEventEmitter.addListener(EVENT_TYPE.ContactlessAuthenticationRequired, async (result) => {
   try {
      await MeaTokenPlatform.requestCardholderVerification();
   } catch (e) {
      ...
   }
});

...

try {
    await MeaTokenPlatform.removeContactlessTransactionListener(cardId);
} catch (e) {
    ...
}				
			

Ahead-of-Time Authentication (Single Tap)

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

Request ahead of time authentication:

				
					MeaTokenPlatform.requestCardholderVerification();				
			

Listen for requested authentication event

				
					import {DeviceEventEmitter} from 'react-native';
import MeaTokenPlatform, {EVENT_TYPE} from 'react-native-mtp';

componentDidMount() {
   MeaTokenPlatform.initAuthenticationListener();

   this.walletPinRequiredListener = DeviceEventEmitter.addListener(EVENT_TYPE.WalletPinRequired,(result) => {
      ...
  });

  this.cardPinRequiredListener = DeviceEventEmitter.addListener(EVENT_TYPE.CardPinRequired,(result) => {
      ...
   });

   this.deviceUnlockRequiredListener = DeviceEventEmitter.addListener(EVENT_TYPE.DeviceUnlockRequired,() => {
      ...
   });

   this.fingerprintRequiredListener = DeviceEventEmitter.addListener(EVENT_TYPE.FingerprintRequired,() => {
      ...
   });

   this.authenticationFailureListener = DeviceEventEmitter.addListener(EVENT_TYPE.AuthenticationListenerFailure, (result) => {
      ...
   });
}

componentWillUnmount() {
   this.walletPinRequiredListener.remove();
   this.cardPinRequiredListener.remove();
   this.deviceUnlockRequiredListener.remove();
   this.fingerprintRequiredListener.remove();
   this.authenticationFailureListener.remove();

   RNMeaTokenPlatform.removeAuthenticationListener();
}				
			

See an example below how to implement and handle EVENT_TYPE.DeviceUnlockRequired event using MeaTokenPlatform.showDeviceUnlockScreen(...) which is based on Android KeyguardManager confirm device credential intent.

				
					this.deviceUnlockRequiredListener = DeviceEventEmitter.addListener(EVENT_TYPE.DeviceUnlockRequired, async () => {
    console.log("DeviceUnlockRequired");

    try {
        await MeaTokenPlatform.showDeviceUnlockScreen("Enter credentials", "This needs to be done for authentication");
    } catch (e) {
        ...
  }
});				
			

Override onActivityResult in activity class:

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

    if (requestCode == REQUEST_CODE_DEVICE_UNLOCK) {
        if (resultCode == RESULT_OK) {
            try {
                MeaTokenPlatform.authenticateWithDeviceUnlock();
            } catch (NotInitializedException e) {
                ...
            }
        }
    }
}				
			

Inform the library that the user is authenticated

After application has authenticated the user, it has to inform the MTP SDK using one of following methods:

  • In case of device unlock authentication:
				
					MeaTokenPlatform.authenticateWithDeviceUnlock()				
			
  • In case of biometric (fingerprint) authentication:
				
					...
this.fingerprintSuccessListener = DeviceEventEmitter.addListener(EVENT_TYPE.AuthenticateWithFingerprintSuccess, () => {
    ...
});

this.fingerprintFailureListener = DeviceEventEmitter.addListener(EVENT_TYPE.AuthenticateWithFingerprintFailure, (e) => {
    ...
});

MeaTokenPlatform.authenticateWithFingerprint()				
			
  • If Wallet PIN is used:
				
					MeaTokenPlatform.authenticateWithWalletPin(pin)				
			
  • If Card PIN is used:

				
					MeaTokenPlatform.authenticateWithCardPin(cardId, pin)				
			

Broadcast receivers

There are five broadcast events that application can listen to, the application can react to these events when it is not even in foreground. There are abstract Broadcast Receiver classes in the MTP SDK, which should be extended and abstract methods overridden.

  • MeaTransactionReceiver
  • MeaOnPinRequestReceiver

The broadcast receivers has to be declared in Android Manifest file.

				
					<receiver
    android:name="com.meawallet.mtp.rn.test.app.receivers.TransactionReceiver"
    android:exported="false">
    <intent-filter>
        <action android:name="com.meawallet.mtp.intent.ON_TRANSACTION_SUBMITTED_MESSAGE" />
        <action android:name="com.meawallet.mtp.intent.ON_TRANSACTION_FAILURE_MESSAGE" />
        <action android:name="com.meawallet.mtp.intent.ON_AUTHENTICATION_REQUIRED_MESSAGE" />
    </intent-filter>
</receiver>
<receiver
    android:name="com.meawallet.mtp.rn.test.app.receivers.PinRequestReceiver"
    android:exported="false">
    <intent-filter>
        <action android:name="com.meawallet.mtp.intent.ON_WALLET_PIN_RESET_REQUEST"/>
        <action android:name="com.meawallet.mtp.intent.ON_CARD_PIN_RESET_REQUEST"/>
    </intent-filter>
</receiver>				
			

Note, for all transaction events MTP SDK always triggers both broadcast event and the listener callback. Application can decide when and which even should be handled. To avoid showing duplicate notifications for the user, application can use TransactionIdHexString field in received MeaContactlessTransactionData object, it is a unique value for each transaction, also presented in failure events.

Implementation example of MeaTransactionReceiver class.

				
					public class TransactionReceiver extends MeaTransactionReceiver {
    private static final String TAG = TransactionReceiver.class.getSimpleName();

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


        MainApplication app = (MainApplication) context.getApplicationContext();
        if (!app.getActivityLifecycleMonitor().isAppRunning()) {

            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);
        } else {

            Intent i = new Intent(ON_TRANSACTION_SUBMITTED_MESSAGE);
            i.putExtra(...);
            ...

            context.sendBroadcast(i);
        }
    }

    @Override
    protected void handleOnTransactionFailureIntent(Context context, String cardId, MeaError error) {

        MainApplication app = (MainApplication) context.getApplicationContext();
        if (!app.getActivityLifecycleMonitor().isAppRunning()) {

            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);
        } else {
            Intent i = new Intent(ON_TRANSACTION_FAILURE_MESSAGE);
            i.putExtra(...);
            ...

            context.sendBroadcast(i);
        }
    }

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

        MainApplication app = (MainApplication) context.getApplicationContext();
        if (!app.getActivityLifecycleMonitor().isAppRunning()) {
            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);
        } else {
            Intent i = new Intent(ON_AUTHENTICATION_REQUIRED_MESSAGE);
            i.putExtra(...);
            ...

            context.sendBroadcast(i);
        }
    }
}				
			

Card deletion

Card deletion can be invoked without a network connectivity using the following methods: * MeaTokenPlatform.markCardForDeletion(cardId) * MeaTokenPlatform.markAllCardsForDeletion()

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

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

				
					try {
    await MeaTokenPlatform.markCardForDeletion(cardId);
} catch (e) {
    ..
}				
			

Update device info

Application should use MeaTokenPlatform.updateDeviceInfo(...) method to send information to WSP, when the device information changes, for example, when Firebase device token is refreshed.

				
					try {
    await MeaTokenPlatform.updateDeviceInfo(token, USER_LOCALE);
} catch (e) {
    ...
}				
			

Transaction limits (Optional)

More: Transaction limits

				
					// Specific currency limits
MeaTokenPlatform.addTransactionLimit(currencyCode: string, amount: number)
MeaTransactionLimit MeaTokenPlatform.getTransactionLimit(currency: string): Promise<MeaTransactionLimit>
MeaTokenPlatform.getTransactionLimits(): Promise<MeaTransactionLimit[]>
MeaTokenPlatform.removeTransactionLimit(currencyCode: string)

// Default limit
MeaTokenPlatform.getDefaultTransactionLimit(): Promise<number>
MeaTokenPlatform.setDefaultTransactionLimit(amount: number)
MeaTokenPlatform.removeDefaultTransactionLimit()

// Clear both currency specific transaction limits and default transaction limit value.
MeaTokenPlatform.clearAllTransactionLimits()				
			

Set wallet PIN (Optional)

More: Set wallet PIN

				
					...
MeaTokenPlatform.initMeaWalletPinListener();

DeviceEventEmitter.addListener(EVENT_TYPE.WalletPinChangeSuccess, () => {
    ...
});
DeviceEventEmitter.addListener(EVENT_TYPE.WalletPinChangeFailed, () => {
    ...
});
DeviceEventEmitter.addListener(EVENT_TYPE.WalletPinSetSuccess, () => {
    ...
});
DeviceEventEmitter.addListener(EVENT_TYPE.WalletPinSetFailed, () => {
    ...
});
DeviceEventEmitter.addListener(EVENT_TYPE.WalletPinResetSuccess, () => {
    ...
});
...
try {
    await MeaTokenPlatform.setWalletPin(newPin);
} catch (e) {
    ...
}				
			

Initialize additional authentication for digitization (Optional)

More: Initialize additional authentication for digitization

				
					import MeaTokenPlatform, {MeaCardState} from 'react-native-mtp';

...

let cardState = await MeaTokenPlatform.getCardState(cardId);
if (MeaCardState.REQUIRE_ADDITIONAL_AUTHENTICATION === cardState) {
    try {
        await MeaTokenPlatform.initializeAdditionalAuthenticationForDigitization(cardId, authenticationMethodId);
        ...
    } catch (e) {
        ...
    }
}
...				
			

Complete additional authentication for digitization (Optional)

More: Complete additional authentication for digitization

				
					...
let cardState = await MeaTokenPlatform.getCardState(cardId);
if (MeaCardState.AUTHENTICATION_INITIALIZED === cardState) {
    try {
        let card = await MeaTokenPlatform.completeAdditionalAuthenticationForDigitization(cardId, authenticationCode);
        ...
    } catch (e) {
        ...
    }
}
...				
			

Get asset (Optional)

More: Get asset

				
					try {
    let meaMediaContents = await MeaTokenPlatform.getAsset(assetId);
} catch (e) {
    ...
}				
			
On this page