Note: Our server and mobile App uses this endpoint https://demostand.okaythis.com as our PSS base url(since we are testing). It is important that these urls are the same, otherwise linking and authorization won't work, as we need them to use the same server.
This application illustrates some simple use cases for enrollment, linking and authorization. It requires a server to function properly, so we built one in Nodejs, which can be found on this repository https://github.com/Okaythis/OkayNodeJs.
The server has three endpoints which are used to inititate enrollment, linking and two different types of authorization(OKAY and PIN).
In order to get the best out of this app, we recommend you download the server and run it locally. All information regarding the installation and testing can be found on this page https://github.com/Okaythis/OkayNodeJs.
The three endpoints we will be using are listed below (I assume you are running the server on your local machine):
// Endpoint for linking a user with Okay Server
http://localhost:4000/link?userExternalId=USER_EXTERNAL_ID
// Endpoint for initiating a simple OKAY authorization
http://localhost:4000/auth?userExternalId=USER_EXTERNAL_ID
// Endpoint for initiating an authorization with PIN
http://localhost:4000/auth/pin?userExternalId=USER_EXTERNAL_ID
OKAY Authorization: This authorization is a very simple auth type that just requires you to click the OKAY button on the auth screen as displayed in the image below to authorize a transaction.
PIN AUTHORIZATION: This authorization type requires a pin in order to complete the authorization flow. The difference between these two authorization types is that OKAY authorization does not require a pin to complete a transaction while PIN authorization requires a pin.
Note: There are more than two authorization types, but for brievity we just explained the ones used in this app.
We also recommend you install Postman for testing your queries, as this is what we will be using for testing. Feel free to use curl or any other tool you feel comfortable with.
Before we use this endpoints, we will need to enroll the user with Okay SDK, and then retreive the user's externalId
from Okay SDK after enrollment or we could just create a UUID
as our externalId
for our user after enrollment.
Given that enrollment is being handled by Okay SDK, we will explain how to initiate enrollment in the Enrollment with Okay SDK on Android section for the SDK below.
An externalId
is a unique identifier the we use to identify different users that are linked to our tenant. Any unique identifier can work here.
To start enrollment just click the Start Enrollment button on the app, and Okay will handle the rest.
After we retreive the externalId
from Okay SDK or UUID
we generated(our generated UUID
becomes our externalId
), we will send this extenalId to our server's linking url to link this user with our tenant(For more information about how we created a tenant for this server, please read the documentation here).
Let us assume we just finished enrolling a new user on the app after clicking the Start Enrollment button on the app, we use xUdKijgIHFnhyi+d7pnabHS8gjHLy/qqkXtOYZkKrpo=
as our user's externalId
. We send this externalId
to this endpoint http://localhost:4000/link
like so:
http://localhost:4000/link?userExternalId=xUdKijgIHFnhyi+d7pnabHS8gjHLy/qqkXtOYZkKrpo=
if linking was succesful after our request, we get a response body like this:
{
"linkingCode": "579813",
"linkingQrImg": "iVBORw0KGgoAAAANSUhEUgAAASwAAAEsAQAAAABRBrPYAAABf0lEQVR42u3aS46DMAwGYCMWLDnCHIWjNUfrUThClywQHsfOAyqoJiGj0Uh/VmnzrUjq2KbEPxkvAgMDAwP7G7aQjZF5I3YyG2Riowe7zXTO87j0wvjpVzfbnwWsAfPPfJpHXXH09RrCdoxgDZk/7eymmcB+g/HahZDcgzVkMYZQx+L9h4+hBqyIxRtQQ7SbLFZfX5RgRSyPVRYeehV+TPPASpjtgkRm8of8od/4ydNfhWB3mUZmN+XITINsh66DtWHyzGmXHoddOP4WwKqYpW0UjnSasLD3GxCsnIWiQ2rnNabHmsi9xxCwKmZ5hTx8f7ZTeqxBG6wFY7a84pAen1UfYJVMVvVIB0+hmgZrwjR0kG1Hqkfk2IPdZmksh46ERJXzIgWsiOXa2WcaqS3sJgZrwWLnJ0TmcLbd2W8BrIKltnAXgomF6IuXGmCVLDd8tn0LCKwZs0xjtWAC1oTt3nj6h6/FnU4I7D7LN2DsW/YXHQywcob/1YCBgYH9Y/YNoQbiC+wLFDIAAAAASUVORK5CYII=",
"status": {
"code": 0,
"message": "OK"
}
}
If this was the kind of response we got, then we will need to retreive the "linkingCode": "579813"
(your linkingCode
may not be the same with "579813"
. Your value may be different) as this is the value we need to finish linking the user. We can proceed to linking this user by passing the linkingCode
into the EditTextField
in the app, then click the Link Manually
button. If all goes well you should see a toast display Linking Successful on the mobile app. If linking was successful, we can now start authorization request from our server.
OKAY Authorization: We can now start our authorization process from our server using the user's externalId
retrieved from the user's enrollment like so.
http://localhost:4000/auth?userExternalId=xUdKijgIHFnhyi+d7pnabHS8gjHLy/qqkXtOYZkKrpo=
If this request was handled successfully, Okay server will send a push notication to the mobile app, that will display the authorization screen, allowing the user to OK the transaction or probably decline the request. You will see a screen like the one below.
PIN Authorization: To start authorization with pin on Okay, we send a request to our server, passing the externalId
as query parameter like so:
http://localhost:4000/auth/pin?userExternalId=xUdKijgIHFnhyi+d7pnabHS8gjHLy/qqkXtOYZkKrpo=
If this request was handled successfully, Okay server will send a push notication to the mobile app that will display the authorization screen, allowing the user to enter a pin in order to complete the transaction or probably decline the request. As of now just type in any pin to complete the transaction. The pin that was entered by the user will be sent as body parameters to your tenant callback(To understand how to set a callback for your tenant please see this documentation). Remember, you are responsible for generating and validating the pins that your users will use in your production server.
Now that we know how these enpoints work we can now call them from our app when we host this server in the cloud.
This is a simple guide on how to integrate and initiate enrollment with Okay SDK.
We will begin by creating a new Android project on Android Studio.
Before we begin enrollment with Okay on our Android app, we will need to add Okay as a dependency to our app level build.gradle
file (i.e app\build.gradle
).
// app/build.gradle
...
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// Okay dependency
implementation 'com.okaythis.sdk:psa:1.1.0'
}
We will also be adding Okay's maven repository to your project level build.gradle
file.
// project/build.gradle
allprojects {
repositories {
google()
jcenter()
maven {
url 'https://dl.bintray.com/okaythis/maven'
}
}
}
We will also need to set up Firebase for our project. If you are not familiar with integrating Firebase messaging please check this documentaion for more information as Okay SDK depends on it.
Update your gradle file to contain this newly added lines of code.
// app/build.gradle
android {
...
// add these lines to your gradle file
dataBinding {
enabled = true
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// Okay dependency
implementation 'com.okaythis.sdk:psa:1.1.0'
// Firebase Dependency
implementation 'com.google.firebase:firebase-messaging:20.0.0'
}
We can now sync our app's gradle file to build.
In order for Okay SDK to work correctly we will need to sync the SDK with PSS. Initialization of the PSA should be done within our Application class, inside the onCreate()
method.
We use the PsaManager
class from Okay to initialize our PSA. We will be using two methods from the PsaManager
class, the init()
and setPssAddress()
methods. The init()
and setPssAddress()
method from the PsaManager
class has the following structure.
PsaManager psaManager = PsaManager.init(Context c, T extends ExceptionLogger)
// The PSS_SERVER_ENDPOINT is the url address for our PSS service e.g https://demostand.okaythis.com/
psaManager.setPssAddress(PSS_SERVER_ENDPOINT);
A typical illustration of how our Application
class should be
// OkayDemoApplication.kt
class OkayDemoApplication: Application() {
override fun onCreate() {
super.onCreate()
initPsa()
}
private fun initPsa() {
val psaManager = PsaManager.init(this, OkayDemoLogger())
psaManager.setPssAddress("https://demostand.okaythis.com")
}
}
This is what my OkayDemoLogger
class looks like.
class OkayDemoLogger: ExceptionLogger {
override fun setUserIdentificator(p0: String?) {
Log.e("SET ID: ", "Successfully set user identificator $p0 ")
}
override fun exception(p0: String?, p1: Exception?) {
Log.e("Exception: ", "Okay Error $p0 -- Exception: $p1")
}
}
We will need to add our application class to our manifest file by adding android:name=".OkayDemoApplication"
to our application tag.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ogieben.okaydemo">
<application
android:name=".OkayDemoApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">
...
</application>
The Okay SDK requires certain kinds of permissions to work properly. We will have to ask the users to grant these permissions before we proceed. We can easily create these helper methods to handle permission resolution for us.
// PermissionHelper.kt
class PermissionHelper(private val activity: Activity) {
val REQUEST_CODE = 204
fun hasPermissions(ctx: Context, permission: Array<String>): Boolean = permission.all {
ActivityCompat.checkSelfPermission(ctx, it) == PackageManager.PERMISSION_GRANTED
}
fun requestPermissions(permission: Array<String>) = ActivityCompat.requestPermissions(activity, permission, REQUEST_CODE)
}
The Okay SDK comes with a prepacked method PsaManager.getRequiredPermissions()
that helps us fetch an array of all required permissions.
// MainActivity.kt
val permissionHelper = PermissionHelper(activity)
private fun checkPermissions() {
// prepacked method
val requiredPermissions = PsaManager.getRequiredPermissions()
if (!permissionHelper.hasPermissions(this, requiredPermissions)) {
permissionHelper.requestPermissions(requiredPermissions)
}
}
We can now use the checkPermission()
method within our MainActivity.kt
's onCreate
method to request for all permission.
// MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
checkPermissions()
}
We will need our device token from Firebase to be able to finish our enrollment.
If we have firebase successfully setup, we could request for our device token using the code sample below. If you have not been able to setup Firebase please refer to this documentaion as Okay requires this service to work correctly.
// MainActivity.kt
private var preferenceRepo: PreferenceRepo = PreferenceRepo(context)
private fun fetchInstanceId () {
FirebaseInstanceId.getInstance().instanceId
.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w("", "getInstanceId failed", task.exception)
Toast.makeText(this@MainActivity, "Error could not fetch token", Toast.LENGTH_LONG).show()
return@OnCompleteListener
}
val token = task.result?.token
// save token to SharedPreference storage for easy retrieval
preferenceRepo.persistAppPns(token.toString())
})
}
If all is good we can now invoke this method from our onCreate()
method within our activity like so.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
// request permissions
checkPermissions()
// fetch token
fetchInstanceId()
}
}
We can now proceed with our enrollment if all permissions have been granted and our appPns(also known as Firebase token) has been retrieved successfully.
private fun beginEnrollment() {
val appPns = preferenceRepo.getAppPns() // retrieve Firebase token from SharedPreference storage
val pubPssB64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxgyacF1NNWTA6rzCrtK60se9fVpTPe3HiDjHB7MybJvNdJZIgZbE9k3gQ6cdEYgTOSG823hkJCVHZrcf0/AK7G8Xf/rjhWxccOEXFTg4TQwmhbwys+sY/DmGR8nytlNVbha1DV/qOGcqAkmn9SrqW76KK+EdQFpbiOzw7RRWZuizwY3BqRfQRokr0UBJrJrizbT9ZxiVqGBwUDBQrSpsj3RUuoj90py1E88ExyaHui+jbXNITaPBUFJjbas5OOnSLVz6GrBPOD+x0HozAoYuBdoztPRxpjoNIYvgJ72wZ3kOAVPAFb48UROL7sqK2P/jwhdd02p/MDBZpMl/+BG+qQIDAQAB"
val installationId = "9990"
val spaEnroll = SpaEnrollData(appPns,
pubPssB64,
installationId,
null,
PsaType.OKAY)
PsaManager.startEnrollmentActivity(this, spaEnroll)
}
In other to retrieve information from the enrollment Activity, we override the onActivityResult()
method within our MainActivity.kt
class.
// MainActivity.kt
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PsaConstants.ACTIVITY_REQUEST_CODE_PSA_ENROLL) {
if (resultCode == RESULT_OK) {
//We should save data from Enrollment result, for future usage
data?.run {
val resultData = PsaIntentUtils.enrollResultFromIntent(this)
resultData.let {
preferenceRepo.saveExternalID(it.externalId)
}
Toast.makeText(applicationContext, "Successfully got this externalId " + resultData.externalId, Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "Error Retrieving intent after enrollment", Toast.LENGTH_SHORT).show()
}
}
}
In order to successfully finish the initialization stage we need to link the user with Okay. This allows us to authorize/authenticate a particular user's action.`
To enable linking on the your app you will need to add this line of code to your app's Application
file.
class OkayDemoApplication: Application() {
override fun onCreate() {
super.onCreate()
initPsa()
// Added this method call
initGatewayServer()
}
private fun initPsa() {
val psaManager = PsaManager.init(this, OkayDemoLogger())
psaManager.setPssAddress("https://demostand.okaythis.com")
}
// Added this method
private fun initGatewayServer() {
GatewayRestServer.init(PsaGsonFactory().create(), "https://demostand.okaythis.com/gateway/")
}
}
This section is divided into two sub-sections. The first section is for developers who do not have a dedicated server of their own but want to implement linking in their apps. While the second section is for developers who have a dedicated server and wants to link their users with Okay.
Since you do not have a server to generate linkingCode
s, you can just generate your linking codes from this url http://spacey.okaythis.com then sign in with "tenant" as your username and "password" as your password. You will receive a linking code that you will pass to PsaManager.linkTenant()
, in order to manually link the current user.
//MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var preferenceRepo: PreferenceRepo
private val permissionHelper = PermissionHelper(this)
private val retrofitWrapper = RetrofitWrapper()
private val transactionHandler = retrofitWrapper.handleTransactionEndpoints()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
preferenceRepo = PreferenceRepo(this)
checkPermissions()
fetchInstanceId()
handleIntent(intent)
enrollmentButton.setOnClickListener { view ->
beginEnrollment()
}
manualLinkButton.setOnClickListener{
// retreive the linkingCode (i.e the linkingCode from editText)
// from the EditTextView entered into the app
val linkingCode = linkingCodeEditText.text.toString()
if(linkingCode.isEmpty()){
Toast.makeText(this, "Linking code can't be empty. Please enter linking code in the input field", Toast.LENGTH_LONG).show()
return@setOnClickListener
}
linkUser(linkingCode)
}
}
fun linkUser(linkingCode: String) {
// grab PsaManager instance
val psaManager = PsaManager.getInstance()
// LinkingScenarioListener listener
val linkingScenarioListener: LinkingScenarioListener = object: LinkingScenarioListener{
override fun onLinkingCompletedSuccessful(var1: Long, var3: String){
Toast.makeText(this@MainActivity, "Linking Successful", Toast.LENGTH_LONG).show()
}
override fun onLinkingFailed(var1: ApplicationState) {
Toast.makeText(this@MainActivity, "Linking not Successful: linkingCode: ${linkingCodeEditText.text} errorCode: ${var1.code} ", Toast.LENGTH_LONG).show()
}
}
// call PsaManager linkTenant method
psaManager.linkTenant(linkingCode, preferenceRepo, linkingScenarioListener)
}
}
We will send a request to our server to start the linking process. We will be sending the externalId generated from Okay SDK as a parameter to our server(To see how we got the externalId
please refer to the enrollment section above). If our request was processed successfully we will recieve a response with the linkingCode
required to complete the linking. The linkingCode
is a six digit number generated for this purpose.
We created a wrapper class called RetrofitWrapper to handle network requests.
//network/RetrofitWrapper.kt
class RetrofitWrapper {
private val BASE_URL = "URL_TO_YOU_SERVER"
fun createClient(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
fun handleTransactionEndpoints(): TransactionEndpoints {
val retrofit: Retrofit = this.createClient()
return retrofit.create(TransactionEndpoints::class.java)
}
}
We make a very simple POST request to our server to initiate the linking process using our retrofit wrapper like so.
//MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var preferenceRepo: PreferenceRepo
private val permissionHelper = PermissionHelper(this)
private val retrofitWrapper = RetrofitWrapper()
private val transactionHandler = retrofitWrapper.handleTransactionEndpoints()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
preferenceRepo = PreferenceRepo(this)
checkPermissions()
fetchInstanceId()
handleIntent(intent)
enrollmentButton.setOnClickListener { view ->
beginEnrollment()
}
linkingButton.setOnClickListener{
startServerLinking(preferenceRepo.externalId)
}
}
private fun startServerLinking(userExternalId: String?) {
transactionHandler.linkUser(userExternalId).enqueue(object: Callback<OkayLinking>{
override fun onFailure(call: Call<OkayLinking>, t: Throwable) {
Toast.makeText(this@MainActivity, "Error making request to Server ${t.localizedMessage}", Toast.LENGTH_LONG).show()
t.printStackTrace()
}
override fun onResponse(call: Call<OkayLinking>, response: Response<OkayLinking>) {
// Retrieve user linkingCode here after network call
// then link user afterwards by passing linkingCode
// to PsaManager.linkingTenant() method
}
})
}
}
After we successfully generated the linking code we can now proceed to linking the user with Okay SDK.
PsaManager provides us with a helper function that allows us to link users with SPS right from Okay SDK. The structure of the method we can use is like so.
PsaManager.linkTenant(linkingCode: String, spaStorage: SpaStorage, linkingScenarioListener: LinkingScenarioListener)
The LinkingScenarioListener must be implemented, as it allows us to listen for two possible events: onLinkingCompletedSuccessful and onLinkingFailed. We will be implementing this listener soon.
We will also need to implement the SpaStorage interface in our application. I think the easiest place to do this, is from one of our repositories(PreferenceRepo class in this case). Of course this is just for convenience.
Below is a typical example of what my PreferenceRepo class might look like.
class PreferenceRepo(context: Context): SpaStorage {
private val prefStorage: SharedPreferences =
context.getSharedPreferences(PREFERENCE_KEY, Context.MODE_PRIVATE)
override fun getPubPssBase64(): String? {
return prefStorage.getString(PUB_PSS_B64, "")
}
override fun putAppPNS(p0: String?) {
with(prefStorage.edit()) {
putString(APP_PNS, p0)
commit()
}
}
override fun putPubPssBase64(p0: String?) {
with(prefStorage.edit()) {
putString(PUB_PSS_B64, p0)
commit()
}
}
override fun getAppPNS(): String? {
return prefStorage.getString(APP_PNS, "")
}
override fun getEnrollmentId(): String? {
return prefStorage.getString(ENROLLMENT_ID, "")
}
override fun putInstallationId(p0: String?) {
with(prefStorage.edit()) {
putString(INSTALLATION_ID, p0)
commit()
}
}
override fun putExternalId(p0: String?) {
with(prefStorage.edit()) {
putString(EXTERNAL_ID, p0)
commit()
}
}
override fun putEnrollmentId(p0: String?) {
with(prefStorage.edit()) {
putString(ENROLLMENT_ID, p0)
commit()
}
}
override fun getInstallationId(): String? {
return prefStorage.getString(INSTALLATION_ID, "")
}
override fun getExternalId(): String? {
return prefStorage.getString(EXTERNAL_ID, "")
}
companion object {
const val PREFERENCE_KEY = "firebase_instance_id"
const val APP_PNS = "app_pns"
const val EXTERNAL_ID = "external_id"
const val PUB_PSS_B64 = "pub_pss_b64"
const val ENROLLMENT_ID = "enrollment_id"
const val INSTALLATION_ID = "installation_id"
}
}
This is a typical way to make a call to PsaManager.linkTenant() method.
// MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var preferenceRepo: PreferenceRepo
private val permissionHelper = PermissionHelper(this)
private val retrofitWrapper = RetrofitWrapper()
private val transactionHandler = retrofitWrapper.handleTransactionEndpoints()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
preferenceRepo = PreferenceRepo(this)
checkPermissions()
fetchInstanceId()
handleIntent(intent)
enrollmentButton.setOnClickListener { view ->
beginEnrollment()
}
linkingButton.setOnClickListener{
startServerLinking(preferenceRepo.externalId)
}
}
...
private fun linkUser(linkingCode: String) {
val psaManager = PsaManager.getInstance()
val linkingScenarioListener: LinkingScenarioListener = object: LinkingScenarioListener{
override fun onLinkingCompletedSuccessful(var1: Long, var3: String){
Toast.makeText(this@MainActivity, "Linking Successful", Toast.LENGTH_LONG).show()
}
override fun onLinkingFailed(var1: ApplicationState) {
Toast.makeText(this@MainActivity, "Linking not Successful: linkingCode: ${linkingCodeEditText.text} errorCode: ${var1.code} ", Toast.LENGTH_LONG).show()
}
}
// pass in linkingCode to PsaManager here to
// initiate the linking process
psaManager.linkTenant(linkingCode, preferenceRepo, linkingScenarioListener)
}
...
private fun startServerLinking(userExternalId: String?) {
transactionHandler.linkUser(userExternalId).enqueue(object: Callback<OkayLinking>{
override fun onFailure(call: Call<OkayLinking>, t: Throwable) {
Toast.makeText(this@MainActivity, "Error making request to Server ${t.localizedMessage}", Toast.LENGTH_LONG).show()
t.printStackTrace()
}
override fun onResponse(call: Call<OkayLinking>, response: Response<OkayLinking>) {
// Retrieve user linkingCode here after netwok call
// we retrieve the user linkingCode here and
// we pass in the linkingCode to linkUser method to start linking
linkUser(response?.body()!!.linkingCode)
}
})
}
}
If we have successfully linked our user we can now proceed to authorizing transactions or authenticating users.
The steps are pretty straight forward. We make a request to our server(SPS) to begin our authorization, Our server will make a call to PSS, PSS in turn, will send a push notification to our app with the current tenantId
of our server and the current transaction sessionId
fields. Once we receive the push notification from Okay Servers we can now proceed to using the SDK's PsaManager.startAuthorizationActivity(activity: Activity, spaAuthorizationData :SpaAuthorizationData)
method, passing in the current instance of our activity and an instance of SpaAuthorizationData class.
Starting an authorization begins with a simple request to your server like so. This is just a simple request using Retrofit in Android.
This is the link to the demo server repository on github Link
//MainActivity.kt
private val retrofitWrapper = RetrofitWrapper() // A simple wrapper around retrofit for network calls
private val transactionHandler = retrofitWrapper.handleTransactionEndpoints()
private fun startServerAuthorization(userExternalId: String?) {
transactionHandler.authorizeTransaction(userExternalId).enqueue(object: Callback<AuthorizationResponse> {
override fun onFailure(call: Call<AuthorizationResponse>, t: Throwable) {
Toast.makeText(this@MainActivity, "Error making request to Server", Toast.LENGTH_LONG).show()
}
override fun onResponse(
call: Call<AuthorizationResponse>,
response: Response<AuthorizationResponse>
) {
Toast.makeText(this@MainActivity, "Request made successfully", Toast.LENGTH_LONG).show()
}
})
}
This code sends a request to our demo server which initiates the authorization with PSS. Our application will recieve a Push Notification that will be handled by our FirebaseMessagingService (This service is part of Firebase messaging, if you are yet to setup Firebase please see this documentaion). In this illustration we extend this service in our app using the OkayDemoFirebaseMessagingService
class.
This is what our OkayDemoFirebaseMessagingService
class looks like.
// OkayDemoFirebaseMessagingService.kt
class OkayDemoFirebaseMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
token?.run {
PreferenceRepo(this@OkayDemoFirebaseMessagingService).putExternalId(token)
}
}
override fun onMessageReceived(remoteData: RemoteMessage) {
if(remoteData.data.isNotEmpty()){
// handle notification
val notificationData = NotificationHandler.extractRemoteData(remoteData)
// You can handle the data from the push notification here
// however you seem fit
// But in this illustration we just send sessionId as an Intent extra to MainActivity
startActivity(Intent(this, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
putExtra(ACTIVITY_WAKE_UP_KEY, notificationData.sessionId!!.toLong() )
})
}
}
override fun onDeletedMessages() {
super.onDeletedMessages()
}
companion object {
val ACTIVITY_WAKE_UP_KEY = "wake_up_key"
}
}
We simply receive this sessionId inside MainActivity.kt
// MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var preferenceRepo: PreferenceRepo
private val permissionHelper = PermissionHelper(this)
private val retrofitWrapper = RetrofitWrapper()
private val transactionHandler = retrofitWrapper.handleTransactionEndpoints()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
preferenceRepo = PreferenceRepo(this)
checkPermissions()
fetchInstanceId()
handleIntent(intent)
enrollmentButton.setOnClickListener { view ->
beginEnrollment()
}
linkingButton.setOnClickListener{
startServerLinking(preferenceRepo.externalId)
}
authorizeButton.setOnClickListener {
startServerAuthorization(preferenceRepo.externalId)
}
}
private fun handleIntent(intent: Intent?) {
intent?.apply {
val sessionId = getLongExtra(OkayDemoFirebaseMessagingService.ACTIVITY_WAKE_UP_KEY, 0)
if (sessionId > 0) {
Toast.makeText(this@MainActivity, "Current sessionId $sessionId ", Toast.LENGTH_LONG).show()
// Start Authorization with retrieved session Id
startAuthorization(sessionId)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PsaConstants.ACTIVITY_REQUEST_CODE_PSA_ENROLL) {
if (resultCode == RESULT_OK) {
//We should save data from Enrollment result, for future usage
data?.run {
val resultData = PsaIntentUtils.enrollResultFromIntent(this)
resultData.let {
preferenceRepo.putExternalId(it.externalId)
}
Toast.makeText(applicationContext, "Successfully got this externalId " + resultData.externalId, Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "Error Retrieving intent after enrollment:- code: ${linkingCodeEditText.text} errorCode: $resultCode", Toast.LENGTH_SHORT).show()
}
}
if (requestCode == PsaConstants.ACTIVITY_REQUEST_CODE_PSA_AUTHORIZATION) {
if (resultCode == RESULT_OK) {
Toast.makeText(this, "Authorization granted", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Authorization not granted", Toast.LENGTH_SHORT).show()
}
}
}
private fun startAuthorization(sessionId: Long) {
// Start authorization here
PsaManager.startAuthorizationActivity(this, SpaAuthorizationData(sessionId,
preferenceRepo.appPNS,
BaseTheme(this).DEFAULT_PAGE_THEME,
PsaType.OKAY))
}
private fun startServerAuthorization(userExternalId: String?) {
transactionHandler.authorizeTransaction(userExternalId).enqueue(object: Callback<AuthorizationResponse> {
override fun onFailure(call: Call<AuthorizationResponse>, t: Throwable) {
Toast.makeText(this@MainActivity, "Error making request to Server", Toast.LENGTH_LONG).show()
}
override fun onResponse(
call: Call<AuthorizationResponse>,
response: Response<AuthorizationResponse>
) {
// Notify user request was sent
Toast.makeText(this@MainActivity, "Request made successfully", Toast.LENGTH_LONG).show()
}
})
}
}
If you successfully retrieved the sessionId
the Authorization process begins immediately, allowing Okay SDK to communicate with PSS.