前端时间接入韩国Onestore平台的时候踩了一些坑,国内的文档又很少,记录一下,废话不多说。我们开始吧。
官方接入文档
官方文档有中英韩三种语言,可移步官方接入文档
开始集成
1】目前的新版本是V5,直接放V5的jar包。从gizub下载OneStore在应用程序中购买SDK
2】清单文件添加
//(必需)添加API版本:它需要显式声明IAPV 5
<meta-data
android:name="iap:api_version"
android:value="5" />
//(可选)指定支付屏幕UI:API版本5支持弹出格式中的支付屏幕UI。如果在下面的iap:view_选项中输入弹出值,则支付窗口将弹出。如果没有设置值,默认情况下它将被视为全屏(Android:value=“Full”)。
<meta-data
android:name="iap:view_option"
android:value="popup | full" >
支付逻辑集成
一、集成步骤
1】初始化API并绑定One Store服务
2】绑定成功,检查是否支持onestore支付,如果支持,继续
3】检查未完成的订单(掉单的情况处理),如果有执行验单,消单逻辑(如果是游戏,也可放到进入游戏后进行)
4】验单:玩家请求支付,支付成功后将有效数据发给服务器进行验证(本地也可进行验证,详见代码)
5】消单:服务器验证成功,告知客户端,客户端进行消单,服务器发送奖励
二、集成代码
初始化,绑定
//需要引入的头文件
import com.onestore.iap.api.IapEnum;
import com.onestore.iap.api.IapResult;
import com.onestore.iap.api.ProductDetail;
import com.onestore.iap.api.PurchaseClient;
import com.onestore.iap.api.PurchaseData;
// onestore 渠道参数配置
private static final int IAP_API_VERSION = 5;
private static final int COIN_PER_GAME = 5;
private static final int PURCHASE_REQUEST_CODE = 1000;
private static final int LOGIN_REQUEST_CODE = 2000;
private static final String PRODUCT_TYPE = IapEnum.ProductType.IN_APP.getType(); // "inapp"
//此处在你的初始化方法中执行
{
// PurchaseClient 初始化——将公钥作为参数传递,以验证context和Signature。
mPurchaseClient = new PurchaseClient(this, mPublicKey);
// 请求绑定ONE store服务,以启动应用内支付。
mPurchaseClient.connect(mServiceConnectionListener);
}
/*
* PurchaseClient的 connect API 回调监听器
* 返回绑定成功或失败以及是否要更新ONE store服务的结果。
*/
PurchaseClient.ServiceConnectionListener mServiceConnectionListener = new PurchaseClient.ServiceConnectionListener() {
@Override
public void onConnected() {
Log.d(TAG, "Service connected");
checkBillingSupportedAndLoadPurchases();
}
@Override
public void onDisconnected() {
Log.d(TAG, "Service disconnected");
}
@Override
public void onErrorNeedUpdateException() {
Log.e(TAG, "connect onError, 需要更新ONE store客户端 ");
updateOrInstallOneStoreService();
}
};
检查是否支持
private void checkBillingSupportedAndLoadPurchases() {
Log.d(TAG, "checkBillingSupportedAndLoadPurchases()");
if (mPurchaseClient == null) {
Log.d(TAG, "PurchaseClient is not initialized");
return;
}
// 方法名字中以postfix加上“Async”,UI线程中为保证安全执行,创建线程处理
this.runOnUiThread(new Runnable() {
@Override
public void run() {
// 先执行 查询是否支持应用内支付
mPurchaseClient.isBillingSupportedAsync(IAP_API_VERSION, mBillingSupportedListener);
}
});
}
/*
* PurchaseClient的isBillingSupportedAsync (查询是否支持)回调监听器
*/
PurchaseClient.BillingSupportedListener mBillingSupportedListener = new PurchaseClient.BillingSupportedListener() {
@Override
public void onSuccess() {
Log.d(TAG, "isBillingSupportedAsync onSuccess");
isBillingSupported = true;
//检查掉单的情况
// queryPurchase(IapEnum.ProductType.IN_APP);
}
@Override
public void onError(IapResult result) {
Log.e(TAG, "isBillingSupportedAsync onError, " + result.toString());
if (IapResult.RESULT_NEED_LOGIN == result) {
loadLoginFlow();
}
}
@Override
public void onErrorRemoteException() {
Log.e(TAG, "isBillingSupportedAsync onError, 无法连接ONE store服务");
}
@Override
public void onErrorSecurityException() {
Log.e(TAG, "isBillingSupportedAsync onError, 应用状态异常下请求支付");
}
@Override
public void onErrorNeedUpdateException() {
Log.e(TAG, "isBillingSupportedAsync onError, 需要更新ONE store客户端");
updateOrInstallOneStoreService();
}
};
发起支付
String devPayload = AppSecurity.generatePayload();
//参数大多数在开头部分说明了,单独说明第9个参数false:是否支持onestore 促销活动
mPurchaseClient.launchPurchaseFlowAsync(IAP_API_VERSION, this, PURCHASE_REQUEST_CODE, goods_id, goods_name,
PRODUCT_TYPE, devPayload_, role_id, false, mPurchaseFlowListener);
/*
* PurchaseClient的 launchPurchaseFlowAsync API (购买)回调监听器
*/
PurchaseClient.PurchaseFlowListener mPurchaseFlowListener = new PurchaseClient.PurchaseFlowListener() {
@Override
public void onSuccess(PurchaseData purchaseData) {
String logStr = "pay success, purchaseData: " + purchaseData.toString();
reportErrorToServer(Code_PayResult, logStr);
// 购买成功后客户端检查开发者payload。
// if (!isValidPayload(purchaseData.getDeveloperPayload())) {
// Log.d(TAG, "launchPurchaseFlowAsync onSuccess, Payload is not valid.");
// return;
// }
// 购买成功后客户端检查签名。
// boolean validPurchase = AppSecurity.verifyPurchase(purchaseData.getPurchaseData(), purchaseData.getSignature(), mPublicKey);
// if (validPurchase) {
// 管理型商品(inapp)购买成功后消耗。
verifyPurchaseToServerAndConsumeItem(purchaseData);
// } else {
// Log.d(TAG, "launchPurchaseFlowAsync onSuccess, Signature is not valid.");
// return;
// }
}
@Override
public void onError(IapResult result) {
if(result == IapResult.RESULT_NEED_LOGIN ) {
//需要登陆onestore
loadLoginFlow();
}
else if(result == IapResult.RESULT_ITEM_UNAVAILABLE){
LogD("purchase failed , item unavailable");
}
else if(result == IapResult.RESULT_ITEM_ALREADY_OWNED){
LogD("purchase failed , item ALREADY_OWNED");
//掉单了
queryPurchase(IapEnum.ProductType.IN_APP);
}
else{
}
}
@Override
public void onErrorRemoteException() {
Log.e(TAG, "queryPurchasesAsync onError, 无法连接ONE store服务");
}
@Override
public void onErrorSecurityException() {
Log.e(TAG, "queryPurchasesAsync onError, 应用状态异常下请求支付");
}
@Override
public void onErrorNeedUpdateException() {
Log.e(TAG, "queryPurchasesAsync onError, 需要更新ONE store客户端 ");
updateOrInstallOneStoreService();
}
};
服务器验单(略) + 客户端消单
private void verifyPurchaseToServerAndConsumeItem(final PurchaseData purchaseData) {
if (mPurchaseClient == null) {
Log.d(TAG, "PurchaseClient is not initialized");
return;
}
......
//服务器验单成功,执行消耗
mPurchaseClient.consumeAsync(IAP_API_VERSION, purchaseData, mConsumeListener);
}
/*
* PurchaseClient的 consumeAsync API (商品消耗)回调监听器
*/
PurchaseClient.ConsumeListener mConsumeListener = new PurchaseClient.ConsumeListener() {
@Override
public void onSuccess(PurchaseData purchaseData) {
Log.d(TAG, "consumeAsync onSuccess, " + purchaseData.toString());
// 商品消耗成功后,按各开发者编写的购买成功方案进行。
}
@Override
public void onErrorRemoteException() {
Log.e(TAG, "consumeAsync onError, 无法连接ONE store服务");
}
@Override
public void onErrorSecurityException() {
Log.e(TAG, "consumeAsync onError, 应用状态异常下请求支付");
}
@Override
public void onErrorNeedUpdateException() {
Log.e(TAG, "consumeAsync onError, 需要更新ONE store客户端 ");
updateOrInstallOneStoreService();
}
@Override
public void onError(IapResult result) {
Log.e(TAG, "consumeAsync onError, " + result.toString());
}
};
其他代码里用到的方法
//未登陆onestore调用
private void loadLoginFlow() {
if (mPurchaseClient == null) {
Log.d(TAG, "PurchaseClient is not initialized");
return;
}
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (mPurchaseClient.launchLoginFlowAsync(IAP_API_VERSION, mActivity, LOGIN_REQUEST_CODE, mLoginFlowListener) == false) {
// listener is null
}
}
});
}
//查询订单
private void queryPurchase(final IapEnum.ProductType productType) {
Log.d(TAG, "queryPurchase() :: productType - " + productType.getType());
if (mPurchaseClient == null) {
Log.d(TAG, "PurchaseClient is not initialized");
return;
}
this.runOnUiThread(new Runnable() {
@Override
public void run() {
mPurchaseClient.queryPurchasesAsync(IAP_API_VERSION, PRODUCT_TYPE, mQueryPurchaseListener);
}
});
}
/*
* PurchaseClient的queryPurchasesAsync API (查询购买记录)回调监听器
*/
PurchaseClient.QueryPurchaseListener mQueryPurchaseListener = new PurchaseClient.QueryPurchaseListener() {
@Override
public void onSuccess(List<PurchaseData> purchaseDataList, String productType) {
Log.d(TAG, "queryPurchasesAsync onSuccess, " + purchaseDataList.toString());
if (IapEnum.ProductType.IN_APP.getType().equalsIgnoreCase(productType)) {
onLoadPurchaseInApp(purchaseDataList);
} else if (IapEnum.ProductType.AUTO.getType().equalsIgnoreCase(productType)) {
// 如为查询购买记录后获取的包月自动支付商品( auto),先验证签名,成功后根据开发者应用处理需求编写方案。
}
}
@Override
public void onErrorRemoteException() {
Log.e(TAG, "queryPurchasesAsync onError, 无法连接ONE store服务");
}
@Override
public void onErrorSecurityException() {
Log.e(TAG, "queryPurchasesAsync onError, 应用状态异常下请求支付");
}
@Override
public void onErrorNeedUpdateException() {
Log.e(TAG, "queryPurchasesAsync onError, 需要更新ONE store客户端 ");
updateOrInstallOneStoreService();
}
@Override
public void onError(IapResult result) {
Log.e(TAG, "queryPurchasesAsync onError, " + result.toString());
}
};
//掉单消耗
private void onLoadPurchaseInApp(List<PurchaseData> purchaseDataList) {
for (PurchaseData purchase : purchaseDataList) {
......
verifyPurchaseToServerAndConsumeItem(purchase);
}
}
//商店更新
private void updateOrInstallOneStoreService() {
PurchaseClient.launchUpdateOrInstallFlow(mActivity);
}
onActivityResult
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
LogD("onActivityResult: " + requestCode + " resultCode: " + resultCode);
switch (requestCode) {
case LOGIN_REQUEST_CODE:
if (resultCode == Activity.RESULT_OK) {
if (mPurchaseClient.handleLoginData(data) == false) {
Log.e(TAG, "onActivityResult handleLoginData false ");
// listener is null
}
} else {
Log.e(TAG, "onActivityResult user canceled");
}
break;
case PURCHASE_REQUEST_CODE:
/*
* 调用 launchPurchaseFlowAsync API 时收到的intent数据通过handlePurchaseData解析返回值。
* 解析后返回结果通过调用 launchPurchaseFlowAsync 时的 PurchaseFlowListener 返回。
*/
if (resultCode == Activity.RESULT_OK) {
if (mPurchaseClient.handlePurchaseData(data) == false) {
Log.e(TAG, "onActivityResult handlePurchaseData false ");
// listener is null
reportErrorToServer(Code_CommonLog, "listener is null");
}
} else {
Log.e(TAG, "onActivityResult user canceled");
reportErrorToServer(Code_CommonLog, "onActivityResult user canceled");
// user canceled , do nothing..
}
break;
default:
}
}
AppSecurity,这个是案例里有的一个类,可以拿来用。
三、集成问题
1、无法拉起支付
1.1】手机必须安装最新版本的OneStore客户端,否则无法支付,自备梯子去拿,分享目前最新版连接
提取码:w0nu
1.2】手机需要翻墙,并且能够进入onestore客户端(可在初始化回调里面查看错误码知晓)
1.3】手机必须支持onestore支付(可在查询是否支持的回调里面查看错误码知晓)
1.4】查看支付回调错误码,常见的错误类型详见代码示例
1.5】查询购买记录,并且进行消费(主要是针对管理型商品(inapp))。注意:如果不进行消费是不能进行购买请求的1
2、 掉单的问题
如果代码逻辑正常,这种情况一般发生在玩家支付过程中网络中断、强退应用等情况。出现这种情况会导致2.1】玩家支付成功,但是没有收到相应的奖励。–未完成验单
2.2】玩家支付成功,没收到奖励,并且还不能继续购买这个商品。–未消单
俗称掉单。掉单了,如果无法处理,我们就只能补单。
为了减少补单的情况发生(总会有掉单),我们需要进行一些规避的方法。在应用启动后或者在发生无法继续购买商品时,重新查询当前是否存在未消耗的商品,如果存在,继续执行验单和消单逻辑,
3、极端操作的情况
3.1】新版本中无法传递透传字段(可以试下payload传递是否可行)
我们项目内商品的分类比较特殊,如果玩家连续多次点击多个不同的商品,会导致错单的情况(订单号跟商品无法匹配),这就需要特殊处理,各位大佬一般用不着,本文不再赘述。
此处内容参考https://blog.csdn.net/Silencemuyi/article/details/90708000,写的也很详细,想必也是被坑的厉害。 ↩︎