How To Integrate

The eSkills platform allows you to integrate the Cache-Based Skilled games business model within your app. We provide you 3 main features:

  • Payment system using appcoins payment system
  • Matchmaking system
  • Revenue share system
    This document will help you to integrate your app with eSkills platform.

Platform Overview

There are 4 components illustrated in this integration:

  • Game Client: game installed in user’s device;
  • Game Server: backend used by the Game Client;
  • AppCoins Wallet: app that allows users to perform payments
  • eSkills: The backend that manages all the matches
24242424

Note: Steps 5 to 7 can be done by the Game client but it’s not secure to do so.

Create URL (1)

In this step is where we set up all the game settings such as the entry fee, user name and other properties. We will use an URL to communicate with AppCoins Wallet and send all the data needed.

📘

All we need to do is create a String object representing the URL containing the essential information to create a match.

URL specification

Scheme: https
Host: apichain.catappult.io
Path: /transaction/eskills
Query string arguments: value, currency, product, domain, metadata, timeout, user_name,
user_id, environment
Example:
https://apichain.catappult.io/transaction/eskills?value=1¤cy=USD&product=summer_battle&user_name=faker&user_id=a25cd286&domain=com.appcoins.eskills2048

ParameterTypeDescriptionStaticOptionalDefaultExample
valuefloatEntry fee valueNY12
currencystringEntry fee currencyYYUSDUSD
productstringMatch type idNY1v1summer_battle
domainstringThe application id,
also known as
package name.
NNcom.appcoins.eskills2048
metadatastringA map in json format with user defined metadataY{"metaKey":"metaValue"}
timeoutlongMax time a match
should run in
seconds
NY360086400
user_namestringUser’s nicknameNYfaker
user_idstringGame user ida25cd286
environmentstringSet the environmentNYLIVESANDBOX
number_of_usersintegerNumber of players per matchYN2

Parameters description:

value: value of the entry fee that will be charged to each player entering the room;
currency: Currency of the value parameter, only USD is supported for now
product: This corresponds to sku. Players will queue up vs players with the same sku and value
domain: your application id (package name)
* metadata: Map<String, String> with user defined metadata.
timeout: max time allowed for a match. Once the time is reached the player with the higher score will win.
user_name: User’s user name. You will be able to get it later from room information.
user_id: Your user id. You will be able to get it later from room information.
environment: Allows you to set the environment. The value can be “LIVE” for releases or
“SANDBOX” for testing purposes. Only players from the same environment can face each other. When playing in “SANDBOX” mode, no entry fee will be charged and no revenue share will take place.
number_of_users: Amount of players per match, including current user.

  • metadata example using google library gson:
Map<String, String> metadataMap = new HashMap<>();
metadataMap.put("metaKey", "metaValue");

Gson gson = new Gson();
String metadata = gson.toJson(metadataMap);

📘

The resulting String URL should look similar to this:

https://apichain.dev.catappult.io/transaction/eskills?value=1.0¤cy=USD&product=1v1&user_id=string_user_id&user_name=Charlie_the_frog&domain=com.appcoins.eskills2048&environment=SANDBOX&metadata={"metaKey":"metaValue"}&number_of_users=2&timeout=3600

🚧

Player usernames must be restricted to the range:

a-z
A-Z
_
and spaces, or else step 3 might fail.

Call URL (2)

Inside the app/game, an intent with the URL should be opened either using the web browser (whenever the AppCoins Wallet is not installed), or via the AppCoins Wallet.

In some cases, the user chooses to open the URL with the browser when the AppCoins Wallet is already installed or even chooses the option to always open the URL with the browser. In order to avoid this, we suggested adding the following sample to trigger the One Step billing flow.

private static final int REQUEST_CODE = 3124;

public void startMatch(String url) {
  Intent intent = buildTargetIntent(url);
  try {
    startActivityForResult(intent, REQUEST_CODE);
  } catch (Exception e) {
    e.printStackTrace();
  }
}

/**
* This method generates the intent with the provided One Step URL to target
the
* AppCoins Wallet.
* @param url The url that generated by following the One Step payment rules
*
* @return The intent used to call the wallet
*/
private Intent buildTargetIntent(String url) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setData(Uri.parse(url));
  // Check if there is an application that can process the AppCoins Billing
    // flow
    PackageManager packageManager =
getApplicationContext().getPackageManager();
    List<ResolveInfo> appsList = packageManager
            .queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
    for (ResolveInfo app : appsList) {
        if (app.activityInfo.packageName.equals("cm.aptoide.pt")) {
            // If there's aptoide installed always choose Aptoide as default to open
            // url
            intent.setPackage(app.activityInfo.packageName);
            break;
            } else if (app.activityInfo.packageName.equals("com.appcoins.wallet")){
            // If Aptoide is not installed and wallet is installed then choose Wallet
            // as default to open url
        intent.setPackage(app.activityInfo.packageName);
        }
    }
  return intent;
}

Target SDK 30 and over

When the app targets SDK 30 or above you need to add the intent to AndroidManifest as such:

<manifest>
...
    <queries>
        <intent>
            <action android:name="android.intent.action.VIEW"/>
        </intent>
    ...
    </queries>
...
</manifest>

Process Payment (3)

Once the URL is called, AppCoins Wallet will take over and process the payment and wait for an opponent to match against the current user. Once the match is ready to start, the AppCoins wallet will return an intent where you can extract a session token that will allow you to interact with our services on the user's behalf.

Extract session token

In order to communicate with eSkills API, you need a session token that will be used as a Bearer token, you should send it over the Authorization header in the requests.
This token is valid according to the value sent on timeout field the default is 12 hours. The session identifies the user and the match he is currently playing.

public static final String SESSION = "SESSION";
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent
data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE && resultCode == 0) {
        String sessionToken = data.getStringExtra(SESSION);
    }
}

Inside "onActivityResult" you can also see that there's a parameter called "resultCode". This represents the payment's current state, as well as any actions you should take. Below you can find all the possible values and their correspondent explanation:

RESULT_OK = 0   // user payed successfuly for the entry ticket
RESULT_USER_CANCELED = 1    // user canceled, before or after paying. if the user canceled after paying, we will automatically refund the transaction
RESULT_REGION_NOT_SUPPORTED = 2 // due to legal limitations, user region (country or sub-division) may not allow platforms like e-Skills 
RESULT_NO_NETWORK = 3   // connection failed while reaching our backend services
RESULT_ERROR = 6    // other errors that were not described earlier

Start Game (4)

Once everything is ready, you should start your game logic.
You must use the session token returned on step 3 to use the eSkills API.
Refer to Best Practices for a hint on how to structure your game's flow.

📘

Checking the room status

At any given time, you can check the room status using the https://api.eskills.catappult.io/room/redoc#operation/get_room__get endpoint.

Periodic Score Update (5)

You should periodically update the user's score by calling our api, everytime you update a user's score, you’ll receive all the match data up to date and you’ll be able to get all the user’s score, the winner (if any) and some other info.
See all the documentation about our api here.

public Single<RoomResponse> patch(String session, long score, UserStatus status) {
    PatchRoomRequest patchRoomRequest = new PatchRoomRequest();

    patchRoomRequest.setScore(score);
    // when updating user's score, status will be "PLAYING"
    patchRoomRequest.setStatus(status);

    //1st argument is the authorization header (see step 3)
    //2nd argument is the request body
    return roomApi.patchRoom(BEARER_ + session, patchRoomRequest);
}

Snippet from RoomRepository.java

Finish Game (6)

Once a user finishes the game by losing or winning, you should call our api informing us. To do so, you need to call set score api setting the status field to “COMPLETED”.
See all the documentation about our api here.

public Single<RoomResponse> patch(String session, long score, UserStatus status) {
    PatchRoomRequest patchRoomRequest = new PatchRoomRequest();

    patchRoomRequest.setScore(score);
    // when finishing the game, status will be "COMPLETED"
    patchRoomRequest.setStatus(status);

    //1st argument is the authorization header (see step 3)
    //2nd argument is the request body
    return roomApi.patchRoom(BEARER_ + session, patchRoomRequest);
}

Snippet from RoomRepository.java

Wait for all players to finish (7)

Once the player finishes the game, you can wait for others to finish the game by calling our api and checking the room status (you can do it by clicking here). Once it is “COMPLETED” a winner has been declared and all the revenue share did take place.

public Single<RoomResult> getRoomResult() {
    return getRoomUseCase.getRoom(session)
        .toObservable()
        .repeatWhen(objectFlowable -> objectFlowable.delay(3, TimeUnit.SECONDS))
        .skipWhile(this::isInProgress)
        .map(RoomResponse::getRoomResult)
        .take(1)
        .singleOrError();
  }

  private boolean isInProgress(RoomResponse roomResponse) {
    boolean completed = roomResponse.getStatus() == RoomStatus.COMPLETED;

    List<User> users = roomResponse.getUsers();
    for (User user: roomResponse.getUsers()) {
      if (user.getStatus() == UserStatus.PLAYING && completed) {
        throw new IllegalStateException("Match Completed but some players are still playing!");
      }
    }
    return !completed;
  }

Snippet from FinishGameActivityViewModel.java

ESkills API

eSkills api provides all the methods needed to update a user's actions. Learn more about our api in the following url: https://api.eskills.catappult.io/room/redoc
For this integration, we need 2 APIs:

Get room stats

You can find the documentation in the following url:
https://api.eskills.catappult.io/room/redoc#operation/get_room__get

Set score

You can find the documentation in the following url:
https://api.eskills.catappult.io/room/redoc#operation/set_store_room_score__patch

Code References

Feel free to check out our working demo app at https://github.com/Aptoide/2048.


What’s Next