OneStore
准备工作!
1,官方文档:https://dev.onestore.co.kr/devpoc/reference/view/Tools
2,https://github.com/ONE-store/inapp-sdk-chi/wiki/Tools-Developer-Guide
3 demo:https://github.com/ONE-store/iap_v5/tree/onestore_iap_sample
4 需要有ONE store服务的设备以及 翻墙
配置商品列表
onestore 提供了两种商品类型:管理型和包月自动支付商品,
管理型商品流程:购买->消耗->再次购买
包月自动支付:购买(订阅)->退订
可通过自定义消耗管理型商品来构建消耗型或者永久型商品
购买流程
1 客户端请求服务器获取商品信息以及自定义订单号
2 客户端发起SDK支付
3 支付完成,客户端将支付结果发送服务器做验证
4 服务器验证成功,针对管理型商品服务器调用消耗接口,消耗商品
客户端配置
cocos2dx引擎
ndk-r11c
build.gradle <project级别>
classpath 'com.android.tools.build:gradle:3.2.0'
gradle/wrapper/gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
buld.gradle <app级别>
compileSdkVersion 28
defaultConfig {
applicationId APPLICATION_ID
minSdkVersion 16
targetSdkVersion 28
externalNativeBuild{...}
}
后台配置
由管理人员完成
需要提供参数:
License Key: (公钥) (客户端/服务器)
Client_ID:(服务器)
Client_secret:(服务器)
android jar:jar file(eg. iap_plugin_v17.01.00_20180206.jar)
客户端接入
官方提供SDK方式和直接实现服务器接口两种方式,这里采用SDK,
客户端实现支付,登陆检测,以及检测漏单
服务器实现支付结果验证,消耗,以及订阅/退订
1 设置 Android Manifest文档
<application ...... >
<!-- One Store SDK begin -->
<meta-data android:name="iap:api_version"
android:value="5" />
<!-- (可选) 选择支付界面(UI):API V5支持弹窗式的支付界面。在iap:view_option里加入popup时,会显示弹窗式的支付画面。 -->
<!-- <meta-data
android:name="iap:view_option"
android:value="popup | full" > -->
<!-- One Store SDK end -->
2 导入jar包,直接放到app/libs下即可,没有此目录的话,自己建一个
3 初始化
在应用的Oncreate方法中调用执行初始化
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// PurchaseClient 初始化——将公钥作为参数传递,以验证context和Signature。
mPurchaseClient = new PurchaseClient(this, AppSecurity.getPublicKey());
// 请求绑定ONE store服务,以启动应用内支付。
mPurchaseClient.connect(mServiceConnectionListener);
}
3.2 退出时需要调用登出
@Override
protected void onDestroy() {
super.onDestroy();
if (mPurchaseClient == null) {
Log.d(TAG, "PurchaseClient is not initialized");
return;
}
// 关闭应用时,使用PurchaseClient中断服务。
mPurchaseClient.terminate();
}
3.3 客户端检测是否可以支付
// ONE store应用内支付API版本
int IAP_API_VERSION = 5;
mPurchaseClient.isBillingSupportedAsync(IAP_API_VERSION, mBillingSupportedListener);
3.4 发起支付
int IAP_API_VERSION = 5;
int PURCHASE_REQUEST_CODE = 1000; // 返回至onActivityResult的request code
String product5000 = "p5000"; // 请求购买的商品ID
String productName = ""; // ""时显示开发者中心注册的商品名称
String productType = IapEnum.ProductType.IN_APP.getType(); // "inapp"
String devPayload = AppSecurity.generatePayload();
String gameUserId = ""; // 默认 ""
boolean promotionApplicable = false;
mPurchaseClient.launchPurchaseFlowAsync(IAP_API_VERSION, this, PURCHASE_REQUEST_CODE, product5000, productName, productType, devPayload, gameUserId, promotionApplicable, mPurchaseFlowListener);
3.5 查询购买记录(检测是否有未消耗商品)
int IAP_API_VERSION = 5;
String productType = IapEnum.ProductType.IN_APP.getType(); // "inapp"
mPurchaseClient.queryPurchasesAsync(IAP_API_VERSION, productType, mQueryPurchaseListener);
3.6 ONE store登录的请求
int IAP_API_VERSION = 5;
int LOGIN_REQUEST_CODE = 2000; // 向onActivityResult 返回的 request code
mPurchaseClient.launchLoginFlowAsync(IAP_API_VERSION, this, LOGIN_REQUEST_CODE, mLoginFlowListener)
3.6.2 登陆结果
switch (reqcode ){
case SDKHelper.REQ_CODE_GOOGLE_LOGIN:
if (resultCode == mActivity.RESULT_OK) {
if (mPurchaseClient.handleLoginData(intent) == false) {
Log.e(TAG, "onActivityResult handleLoginData false ");
}
} else {
Log.e(TAG, "onActivityResult user canceled");
}
break;
default:
}
服务器处理
1 服务器验证,one store采用sha512公钥验证
2 服务器消耗
URI: https://{host}/v2/purchase/consume/{purchaseId}/{packageName}
3 变更订阅状态
URI: https://{host}/v2/purchase/manage-payment-status/{purchaseId}/{packageName}/{action}
客户端代码
APPactivity,java
package org.cocos2dx.lua;
import android.content.Intent;
import android.os.Bundle;
import org.cocos2dx.lib.Cocos2dxActivity;
public class AppActivity extends Cocos2dxActivity{
private SDKHelper mSDKHelper = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.setEnableVirtualButton(false);
super.onCreate(savedInstanceState);
// Workaround in https://stackoverflow.com/questions/16283079/re-launch-of-activity-on-home-button-but-only-the-first-time/16447508
if (!isTaskRoot()) {
// Android launched another instance of the root activity into an existing task
// so just quietly finish and go away, dropping the user back into the activity
// at the top of the stack (ie: the last state of this task)
// Don't need to finish it again since it's finished in super.onCreate .
return;
}
// DO OTHER INITIALIZATION BELOW
// 初始化渠道SDK
this.initSDKHelper();
}
@Override
protected void onStart() {
super.onStart();
if (mSDKHelper != null){
mSDKHelper.onStart();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mSDKHelper != null ){
mSDKHelper.onDestroy();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (mSDKHelper != null ){
mSDKHelper.onActivityResult(requestCode,resultCode,data);
}
}
private void initSDKHelper(){
mSDKHelper = new SDKHelper();
mSDKHelper.initSDK(this);
}
}
2 SDKHelper.java
public class SDKHelper {
private static String TAG = SDKHelper.class.getSimpleName();
private static Cocos2dxActivity mActivity = null;
private static OneStoreHelper mOneStoreHelper = null;
public static final int REQ_CODE_ONE_STORE_LOGIN = 1000;
public static final int REQ_CODE_ONE_STORE_PURCHAS= 1001;
public void initSDK(Cocos2dxActivity context)
{
mActivity = context;
// one store pay
mOneStoreHelper = new OneStoreHelper();
mOneStoreHelper.initSDK(context);
}
public void onDestroy(){
mOneStoreHelper.onDestroy();
}
public void onActivityResult(int reqcode, int resultCode, Intent intent){
mOneStoreHelper.onActivityResult(reqcode,resultCode,intent);
}
public static void runOnGL(final int luaFunc, final String retData){
if (luaFunc == -1 || luaFunc == 0) return;
mActivity.runOnGLThread(new Runnable() {
@Override
public void run() {
Cocos2dxLuaJavaBridge.callLuaFunctionWithString(luaFunc,retData);
Cocos2dxLuaJavaBridge.releaseLuaFunction(luaFunc);
}
});
}
// ------------------------------------------------------------
// lua 调用 android
// ------------------------------------------------------------
public static int test(final int luaFunc){
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i(TAG, "test----->");
}
});
return 1;
}
// 返回SDK是否成功
public static boolean checkSDKInit(){
Log.d(TAG,"checkinit===");
// return false;
return mOneStoreHelper.mInitResult && mOneStoreHelper.mBillingSupported;
}
public static void onLoginPayment(final String args ,final int luaFunc){
Log.d(TAG,"onLoginPayment");
JSONObject jsonObject = new JSONObject();
if (checkSDKInit() == true) {
try{
jsonObject.put("code",1);
jsonObject.put("error_msg","sdk unInit");
} catch (JSONException e) {
e.printStackTrace();
}
runOnGL(luaFunc,jsonObject.toString());
Log.d(TAG,"onLoginPayment 1111");
return;
};
boolean ret = false;
if (mOneStoreHelper.mInitResult == false){
Log.d(TAG,"onLoginPayment 2222");
mOneStoreHelper.initWithCallback(luaFunc);
}else if(mOneStoreHelper.mBillingSupported == false){
Log.d(TAG,"onLoginPayment 333");
mOneStoreHelper.checkBillingSupportedAndLoadPurchases(luaFunc);
}else {
try{
jsonObject.put("code",0);
} catch (JSONException e) {
e.printStackTrace();
}
Log.d(TAG,"onLoginPayment 4444");
runOnGL(luaFunc,jsonObject.toString());
}
}
public static void onPayment(final String args,final int luaFunc){
mOneStoreHelper.onPayment(args,luaFunc);
}
public static void queryPurchases(final String args,final