一步付款 (OSP)

一步付款(OSP)是一种实施Catappult计费的简单易用的解决方案。 它包含一个URL,该URL可启动AppCoins Wallet应用处理付款,然后根据付款结果通知您将商品提供给最终用户。

总结

具备OSP功能的应用的计费流程如下:

  1. 最终用户尝试在您的应用上购买产品;
  2. 应用将您的OSP URL作为意图调用,进而启动一步付款计费流程;
  3. AppCoins Wallet读取OSP URL意图,处理付款,然后在完成时调用您的Web服务端点;
  4. 您的Web服务验证交易数据;
  5. 您将产品提供给最终用户。
1280

接下来在您的应用上实施OSP,您的第一个目标是搜索您的应用启动计费流程的位置。 每当最终用户选择要购买的产品时,您的应用代码中就有个计费流程启动位置。 通常情况下,该流程会创建一个意图,以便最终用户可以在外部应用上完成购买付款。

既然您已经找到您的应用启动计费流程的位置,是时候开始实施一步付款了。 此实施操作包括3个步骤:

  1. 生成您的OSP URL;
  2. 创建意图以处理付款;
  3. 创建要用作回调URL的Web服务端点。

📘

Unity集成

如果您使用Unity构建游戏,请在此处查看我们的文档,了解如何在Unity中集成一步付款:Unity OSP

1. 生成您的OSP URL

实施一步付款的第一步包括生成您的OSP URL。 OSP URL调用的服务是https://apichain.catappult.io/transaction/inapp但有一些查询参数需要填写,如下表所示:

名称类型描述必须示例
产品字符串正在购买的产品名称(又名 SKU)。\n它只能包含小写字母、数字、下划线 (_) 和英文句点 (.)sword.001
域名字符串应用ID,也称为应用包名称。com.appcoins.trivialdrivesample
callback_url字符串交易完成后调用URL的URL编码版本。https%3A%2F%2Fwww.mygamestudio.com%2FcompletePurchase%3FuserId%3D1234是 https://www.mygamestudio.com/completePurchase?userId=1234的URL编码版本
order_reference字符串开发者创建的交易的唯一标识符(不能用于不同的购买)。XYZ98880032
签名字符串用于验证的签名URL的十六进制字符串。\n签名必须为小写。49bc6dac9780acfe5419eb16e862cf096994c15f807313b04f5a6ccd7717e78e
价值数字所选产品的价值。
货币字符串发送款项所用的货币。 须遵守ISO 4217

最后应该是这样的:

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

请注意,签名参数是通过使用SHA256算法使用HMAC函数签名来构建的。 此过程所需的密钥应仅在服务器级别可用,而且应该在开发人员和提供商之间共享。 如想了解有关密钥管理的更多信息,请单击此处。 因此,我们强烈建议您在服务器级别(例如:在Web服务端点上)生成OSP URL。 然后,您的应用必须在创建意图之前向您的服务器请求生成的OSP URL。

🚧

产品和价格应在Catappult上注册,以便钱包获得购买价值。 如果应用内产品未在Catappult中注册,并且自动获取处于打开状态,那么它将从Google Play获取产品的美元价值。 如果找不到价值或自动获取功能关闭,建议您手动注册。

下面是您需要在服务器上实现的功能示例,来使用不同的编程语言生成签名的OSP URL:

$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. 创建意图以处理付款

您已经生成了OSP URL,现在可以用其创建意图,并请求其由应用的当前活动处理。

如果设备上安装了AppCoins Wallet,您可以请求它处理已创建的意图。 否则,将由设备的默认Web浏览器处理意图。 这部分的实施应如下所示:

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, sku: String, value: Number, currency: String): String {
   // 待办事项:发送请求以从您的服务器获取OSP URL,然后将其返回
  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 sku, float value, String currency) {
    // 待办事项:发送请求以从您的服务器获取OSP URL,然后将其返回
  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;
}

注意:如果您的应用的目标是SDK 30或更高版本,那么您需要将意图添加到AndroidManifest,如下所示:

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

此时由AppCoins Wallet负责处理付款过程。 最终用户要么使用其现有积金支付,要么使用一种可用方式为其钱包充值,然后再完成支付。

3. 创建要用作回调URL的Web服务端点

AppCoins Wallet上的交易完成后,将向您在OSP URL的查询字符串参数callback_url中指定的Web服务端点发出POST请求。 在此POST请求的正文中,将发送一个包含名为transaction的字段的JSON对象。

交易字段也是个JSON对象,应该进行解析,以便获取有关刚刚完成的交易的信息。

名称类型说明示例
uid字符串交易资源的唯一IDB27YBHAHN2G3J6RE
域名字符串程序包名称com.appcoins.trivialdrivesample
产品字符串产品名称(又名SKU)sword.001
参考字符串开发人员创建的交易的唯一标识符。XYZ98880032
状态字符串交易状态已完成,或已取消,或失败
已添加字符串交易添加了时间戳2020-04-18T06:15:18+00:00
已修改字符串交易修改了时间戳2020-04-18T07:17:19+00:00
类型字符串交易类型INAPP_UNMANAGED
price.appc字符串以AppCoins表示的交易价格115
price.currency字符串交易价格货币(由最终用户用来进行购买)美元、APPC、欧元等
price.value字符串交易价格值11.5
price.usd字符串以美元表示的交易价格4.99

要验证数据完整性,您可以在自己的Web服务上向我们交易的API (https://api.catappult.io/broker/8.20220927/transactions/)发出GET请求,并在该请求中传递交易UID(例如:https://api.catappult.io/broker/8.20220927/transactions/2DtyvTOSShc1xT9C)。 返回的数据必须等于回调URL在其主体上收到的交易数据。 最后,一旦您在服务器上完成了所有验证,您将需要通知您的应用,并将商品提供给最终用户。

常见问题解答

如何从Catappult获取您的产品价格?
要从Catappult获取您的产品详细信息(说明、价格等),您可以在https://api.catappult.io/productv2/8.20220928/applications/${DOMAIN}/inapp/consumables/${PRODUCT_ID}向我们的API发出GET请求,其中字段${DOMAIN}应填写您的应用程序包名称,字段${PRODUCT_ID}则填写您的产品ID(例如:https://api.catappult.io/productv2/8.20220928/applications/com.appcoins.trivialdrivesample/inapp/consumables/gas,其中com.appcoins.trivialdrivesample是我们的应用程序包名称,而gas是我们的产品ID)。

同一个应用内商品可以有多个不同的价格/货币吗?
是。 在应用内产品菜单中,可以添加新产品和编辑现有产品。 要查看如何在Catappult上向同一产品添加多种价格和货币,请点击此处。 当产品在Catappult中注册了多种货币时,我们建议您不要在URL中设置价值和货币。 如果您确实在URL上设置,这可能会导致值不匹配,因为如果在Catappult中注册,URL上的货币必须与用户的货币相匹配