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.

📘

Android Studio plug-in

To help implement One-Step Payment, we have developed an Android Studio plug-in that will guide you step-by-step. The plug-in can be downloaded here and you can read more about this plug-in here.

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 https://apichain.catappult.io/transaction/inapp but there are some query parameters that you will need to fill in, as you can see in the table below:

NameTypeDescriptionMandatoryExample
productStringThe name of the product (aka SKU) being bought.
It can only have lowercase letters, numbers, underscores (_) and periods (.)
Ysword.001
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 completed.Yhttps%3A%2F%2Fwww.mygamestudio.com%2FcompletePurchase%3FuserId%3D1234 which is the URL encoded version of https://www.mygamestudio.com/completePurchase?userId=1234
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.
Y49bc6dac9780acfe5419eb16e862cf096994c15f807313b04f5a6ccd7717e78e
valueNumericThe value of the chosen product.N
currencyStringThe currency in which the value is sent. It follows ISO 4217.N

In the end, it should look like this:

https://apichain.catappult.io/transaction/inapp?product=sword.001&domain=com.appcoins.trivialdrivesample&callback_url=https%3A%2F%2Fwww.mygamestudio.com%2FcompletePurchase%3FuserId%3D1234&signature=91e3488303d93eb637e57f6abb7908837b9d8a3144261aad4b2247de3b1c525a

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.

🚧

Products and prices should be registered on Catappult so the wallet can get the values for the purchase. If the in-app products are not registered in Catappult and the auto-fetch is on, it will fetch the USD value for that product from Google Play. If it can not find the value or the auto-fetch feature is off, you should register manually.

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:

$product = 'sword.001';
$domain = 'com.appcoins.trivialdrivesample';
$callback_url = 'https://www.mygamestudio.com/completePurchase?userId=1234';
$encoded_callback_url = urlencode($callback_url);

$url = 'https://apichain.catappult.io/transaction/inapp';
$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 product = 'sword.001';
let domain = 'com.appcoins.trivialdrivesample';
let callback_url = 'https://www.mygamestudio.com/completePurchase?userId=1234';
let encoded_callback_url = encodeURIComponent(callback_url);

let url = 'https://apichain.catappult.io/transaction/inapp';
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

product = "sword.001"
domain = "com.appcoins.trivialdrivesample"
callback_url = "https://www.mygamestudio.com/completePurchase?userId=1234"
encoded_callback_url = urllib.parse.quote(callback_url, safe="")

url = "https://apichain.catappult.io/transaction/inapp"
url += "?product=" + product
url += "&domain=" + domain
url += "&callback_url=" + encoded_callback_url

secret_key = b'secret'
signature = hmac.new(secret_key, 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 product = "sword.001"
    
    val ospUrl = generateOspUrl(domain, product) 
    
    val intent = Intent(Intent.ACTION_VIEW)
    intent.data = Uri.parse(ospUrl)
    
    if (isAppCoinsWalletInstalled(activity)) {
        intent.setPackage("com.appcoins.wallet")
    }
    activity.startActivityForResult(intent, 10003)
  } catch (e: Exception) {
    e.printStackTrace()
  }
}

private fun generateOspUrl(domain: String, product: String): String {
  // TODO: Send a request to obtain the OSP URL from your server and then return it
  return "https://apichain.catappult.io/transaction/inapp?domain=com.appcoins.trivialdrivesample&product=sword.001&callback_url=https%3A%2F%2Fmygamestudio.co%2Fappcoins%3Fout_trade_no%3D1234&signature=49bc6dac9780acfe5419eb16e862cf096994c15f807313b04f5a6ccd7717e78e"
}

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 product = "sword.001";
    
    String ospUrl = generateOspUrl(domain, product);
    
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setData(Uri.parse(ospUrl));
    
    if (isAppCoinsWalletInstalled(activity)) {
        intent.setPackage("com.appcoins.wallet");
    }
    activity.startActivityForResult(intent, 10003);
  } catch (Exception e) {
    e.printStackTrace();
  }
}

private static String generateOspUrl(String domain, String product) {
  // TODO: Send a request to obtain the OSP URL from your server and then return it
  return "https://apichain.catappult.io/transaction/inapp?domain=com.appcoins.trivialdrivesample&product=sword.001&callback_url=https%3A%2F%2Fmygamestudio.co%2Fappcoins%3Fout_trade_no%3D1234&signature=49bc6dac9780acfe5419eb16e862cf096994c15f807313b04f5a6ccd7717e78e";
}

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:

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

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.

This POST request requires a 200 response code acknowledgement otherwise it will keep doing retries using the exponential retry algorithm.

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.

NameTypeDescriptionExample
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 CHARGEBACK
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

COMPLETED and CHARGEBACK are the two possible states that are notified via callback, these occur when a purchase or chargeback were successfully completed respectively. When you receive a COMPLETED transaction your backend should give the item to the user. When you get a CHARGEBACK transaction your backend should take away the item from the user.

Please note that besides COMPLETED and CHARGEBACK there are also FAILED and REFUNDED status, these two statuses however ARE NOT notified via callback.

To verify data integrity, on your web service, you can make a GET request to our transaction's API https://api.catappult.io/broker/8.20220927/transactions/ where you pass the transaction UID (example: https://api.catappult.io/broker/8.20220927/transactions/2DtyvTOSShc1xT9C).
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.

FAQ

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 https://api.catappult.io/productv2/8.20220928/applications/${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: https://api.catappult.io/productv2/8.20220928/applications/com.appcoins.trivialdrivesample/inapp/consumables/gas 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.
When a product has multiple currencies registered in Catappult, we recommend that you don't set the value and currency in the URL. If you do set it on the URL, this may lead to values mismatching because the currency on the URL has to match the user's currency if it is registered in Catappult.

Are there any helpers to implement the SDK?
Yes, there is an Android Studio plug-in that will guide you step-by-step. The plug-in can be downloaded here.