One-Step Payment

One-Step Payment (OSP) is a simple and easy solution to implement Catappult billing. It consists of a URL that launches the AppCoins Wallet app to process the payment and then tells you to give the item to the end-user, depending on the result.

In Summary

The billing flow of your application with OSP is as follows:

  1. End-user tries to buy a product on your application;
  2. Application launches the One-Step Payment billing flow by calling your OSP URL as an Intent;
  3. The AppCoins Wallet reads OSP URL Intent, handles the payment, and on completion calls your web service endpoint;
  4. Your web service validates the transaction data;
  5. You give the product to the end user.

Moving on to the implementation of OSP on your application, your first goal is to search where your application launches the billing flow. Whenever an end-user selects a product to be purchased there is a place on your application’s code where a billing flow is launched. Usually, it creates an Intent so that the end-user can complete its purchase payment on an external application.

Now that you found where your application launches the billing flow, it's time to start implementing One-Step Payment. This implementation consists of 3 steps:

  1. Generate your OSP URL;
  2. Create an Intent to process the payment;
  3. Create a web service endpoint to be used as the callback URL.


Unity integration

If you use Unity to build your game, check our documentation on how to integrate One-Step Payment in Unity here: Unity OSP.

1. Generate your OSP URL

The first step in implementing One-Step Payment consists of generating your OSP URL. The service to be called by the OSP URL is but there are some query parameters that you will need to fill in, as you can see in the table below:

valueNumericThe value of the chosen product.Y15.99
currencyStringThe currency in which the value is sent. It follows ISO 4217.YUSD or EUR
productStringThe name of the product (aka SKU) being bought.
It can only have lowercase letters, numbers, underscores (_) and periods (.)
domainStringThe application id, also known as app package name.Ycom.appcoins.trivialdrivesample
callback_urlStringThe URL encoded version of the URL to be called after the transaction is which is the URL encoded version of
order_referenceStringUnique identifier of the transaction created by the developer (cannot be used for different purchases).NXYZ98880032
signatureStringThe Hexadecimal string of the signed URL in order to be validated.
The signature must be lowercase.

In the end, it should look like this:

Note that the signature parameter is built by signing using an HMAC function with the use of the SHA256 algorithm. The required secret key for this process should be available only at the server level and should be shared between the developer and provider. To know more about Secret Key Management, click here.

For this reason, we strongly recommend you generate your OSP URL on the server level, (for example: on a web service endpoint). Your application must then request your server for the generated OSP URL before creating the Intent.

Below there are examples of the functionality that you will need to implement on your server to generate your signed OSP URL in different programming languages:

$value = 15.99;
$currency = 'USD';
$product = 'sword.001';
$domain = 'com.appcoins.trivialdrivesample';
$callback_url = '';
$encoded_callback_url = urlencode($callback_url);

$url = '';
$url .= '?value=' . $value;
$url .= '&currency=' . $currency;
$url .= '&product='.$product;
$url .= '&domain='.$domain;
$url .= '&callback_url='.$encoded_callback_url;

$SECRET_KEY = 'secret';
$signature = hash_hmac('sha256', $url, $SECRET_KEY, false);
$signed_url = $url.'&signature='.$signature;
const crypto = require('crypto');

let value = 15.99;
let currency = 'USD';
let product = 'sword.001';
let domain = 'com.appcoins.trivialdrivesample';
let callback_url = '';
let encoded_callback_url = encodeURIComponent(callback_url);

let url = '';
url += '?value=' + value;
url += '&currency=' + currency
url += '&product=' + product;
url += '&domain=' + domain;
url += '&callback_url=' + encoded_callback_url;

let secret_key = 'secret';
let signature = crypto.createHmac("sha256", secret_key).update(url).digest('hex');
let signed_url = url + '&signature=' + signature;
import urllib.parse
import hmac
import hashlib

value = 15.99
currency = "USD"
product = "sword.001"
domain = "com.appcoins.trivialdrivesample"
callback_url = ""
encoded_callback_url = urllib.parse.quote(callback_url, safe="")

url = ""
url += "?value=" + str(value)
url += "&currency=" + currency
url += "&product=" + product
url += "&domain=" + domain
url += "&callback_url=" + encoded_callback_url

secret_key = b'secret'
signature =, url.encode("utf-8"), hashlib.sha256).hexdigest()
signed_url = url + "&signature=" + signature

2. Create an Intent to process the payment

Now that you have generated your OSP URL, it's time to create an Intent with it and request for it to be processed by the application’s current Activity.

If the AppCoins Wallet is installed on the device, you can request it to process the created Intent. Otherwise, the Intent will be processed by the device’s default Web Browser. Your implementation for this part should look something like this:

fun launchOsp(activity: Activity) {
  try {
    val domain = "com.appcoins.trivialdrivesample"
    val sku = "sword.001"
    val value = 1f
    val currency = "USD"
    val ospUrl = generateOspUrl(domain, sku, value, currency) 
    val intent = Intent(Intent.ACTION_VIEW) = Uri.parse(ospUrl)
    if (isAppCoinsWalletInstalled(activity)) {
    activity.startActivityForResult(intent, 10003)
  } catch (e: Exception) {

private fun generateOspUrl(domain: String, sku: String, value: Number, currency: String): String {
  // TODO: Send a request to obtain the OSP URL from your server and then return it
  return ""

private fun isAppCoinsWalletInstalled(activity: Activity): Boolean {
    val packageManager = activity.applicationContext.packageManager
    val intentForCheck = Intent(Intent.ACTION_VIEW)
    if (intentForCheck.resolveActivity(packageManager) != null) {
        try {
            packageManager.getPackageInfo("com.appcoins.wallet", PackageManager.GET_ACTIVITIES)
            return true
        } catch (e: PackageManager.NameNotFoundException) {}
    return false
public static void launchOsp(Activity activity) {
  try {
    String domain = "com.appcoins.trivialdrivesample";
    String sku = "sword.001";
    float value = 1.0f;
    String currency = "USD";
    String ospUrl = generateOspUrl(domain, sku, value, currency);
    Intent intent = new Intent(Intent.ACTION_VIEW);
    if (isAppCoinsWalletInstalled(activity)) {
    activity.startActivityForResult(intent, 10003);
  } catch (Exception e) {

private static String generateOspUrl(String domain, String sku, float value, String currency) {
  // TODO: Send a request to obtain the OSP URL from your server and then return it
  return "";

private static boolean isAppCoinsWalletInstalled(Activity activity) {
    PackageManager packageManager = activity.getApplicationContext().getPackageManager();
    Intent intentForCheck = new Intent(Intent.ACTION_VIEW);
    if (intentForCheck.resolveActivity(packageManager) != null) {
        try {
            packageManager.getPackageInfo("com.appcoins.wallet", PackageManager.GET_ACTIVITIES);
            return true;
        } catch (PackageManager.NameNotFoundException e) {}
    return false;

NOTE: If your application targets SDK 30 or above then you need to add the intent to AndroidManifest as such:

    <package android:name="com.appcoins.wallet" />

This is the moment at which the AppCoins Wallet takes care of the payment process. The end-user will either use their existing credits or top up their wallet with one of the methods available and then complete the payment.

3. Create a web service endpoint to be used as the callback URL

Once the transaction is completed on the AppCoins Wallet, a POST request will be made to the web service endpoint you specified on the query string parameter callback_url of your OSP URL. On the body of this POST request, a JSON object will be sent with a field named transaction.

The transaction field is also a JSON object and should be parsed so that you can get the information about the transaction that has just been completed.

uidStringUnique ID for transaction resourceB27YBHAHN2G3J6RE
domainStringPackage namecom.appcoins.trivialdrivesample
productStringProduct name (aka SKU)sword.001
referenceStringUnique identifier of the transaction created by the developer.XYZ98880032
statusStringTransaction statusCOMPLETED or CANCELED or FAILED
addedStringTransaction added timestamp2020-04-18T06:15:18+00:00
modifiedStringTransaction modified timestamp2020-04-18T07:17:19+00:00
typeStringType of transactionINAPP_UNMANAGED
price.appcStringTransaction price in AppCoins115
price.currencyStringTransaction price currency (used by the end-user to perform the purchase)USD, APPC, EUR, etc
price.valueStringTransaction price value11.5
price.usdStringTransaction price in USD4.99

To verify data integrity, on your web service, you can make a GET request to our transaction's API where you pass the transaction UID (example:
The returned data must be equal to the transaction data the callback URL received on its body.
Finally, once you do all the validations on your server, you will need to notify your application and give the item to the end-user.


How to obtain your product prices from Catappult?
To obtain your product details (description, prices, etc.) from Catappult you can make a GET request to our API on${DOMAIN}/inapp/consumables/${PRODUCT_ID}, where on the field ${DOMAIN} you pass your app package name and on the field ${PRODUCT_ID} your product id (example: where com.appcoins.trivialdrivesample is our app package name and gas is our product id).

Can I have more than several different prices/currencies on the same in-app product?
Yes. In the in-app products menu, it is possible to add new products and edit existing products. To see how to add several prices and currencies to the same product on Catappult, click here.