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
**Note:** Steps 5 to 7 can be done by the Game client but it’s not secure to do so.**Note:** Steps 5 to 7 can be done by the Game client but it’s not secure to do so.

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

Parameter

Type

Description

Static

Optional

Default

Example

value

float

Entry fee value

N

Y

1

2

currency

string

Entry fee currency

Y

Y

USD

USD

product

string

Match type id

N

Y

1v1

summer_battle

domain

string

The application id,
also known as
package name.

N

N

com.appcoins.eskills2048

metadata

string

A map in json format with user defined metadata

Y

{"metaKey":"metaValue"}

timeout

long

Max time a match
should run in
seconds

N

Y

3600

86400

user_name

string

User’s nickname.

N

Y

faker

user_id

string

Game user id

a25cd286

environment

string

Set the environment

N

Y

LIVE

SANDBOX

number_of_users

integer

Number of players per match

Y

N

2

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. Only characters in the range [a-zA-Z0-9_ ] are allowed.
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 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
RESULT_INVALID_URL = 7 // URL is ill defined and/or contains invalid parameters
RESULT_INVALID_USERNAME = 8 // user_name url parameter contains invalid characters

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 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 check room status, 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