Android 海外应用内支付之ONE store(韩国支付SDK)集成封装

前提:

之前写过一篇Android Google应用内支付,刚好公司的主营业务是海外app开发。前段时间也需要支持了韩国那边的应用内支付,给的文档上面讲了需要集成onestore支付。由于对韩国那边的应用内支付没有什么概念,所以就百度了一番。奈何,文档太少或者写的不是很清晰(难道能说是因为自己不太理解)。所以,自己就看着官方文档做了集成了一番,踩了不少坑。但是,总体来说,比较官方文档讲的比较清楚。好了,讲了这么多,下面看如何集成吧。

首先

  • 1、查看文档
    要做ONE store集成开发,第一步当然是查看官方文档了,我这里集成的是最新的版本,也就是v5版本。也有之前的v3和v4等版本,但是,人要向前看不是。所以,就集成了最新的版本了。
onestore官方文档.png
onestore官方文档集成.png
  • 2、下载ONE store的jar包(在如下图所示的地方下载对应的开发包和官方demo)
    题外话:感觉官方的demo写的真的不咋地,还没有文档写的清楚,并且让人看得云里雾里的。

image.png

注意:官方jar包下载地址在github上面,不清楚的可以点这里查看
jar下载地址.png

注意:上面红框标注的就是我们需要的jar包

  • 3、支付接入文档(两种实现方式)
    1、调用AIDL接口实现
    2、使用SDK实现(建议使用第二种,笔者使用的就是第二种)


    image.png

其次

上面讲了那么多,那么看看官方文档的接入到底是个什么样子的呢?下面是目录截图


image.png

+1、在看到了上面的截图之后,我们这里讲一下支付的整个流程。

  • 第一步:安装ONE store客户端,如果不安装ONE store的客户端是不能支付的,并且需要自备梯子。
  • 第二步:初始化ONE store,如果初始化失败或者连接不到ONE store也是不能调起ONE store支付的。
  • 第三步:查询是否支持ONE store支付,如果支持再进行下一步。
  • 第四步:查询购买记录,并且进行消费(主要是针对管理型商品(inapp))。注意:如果不进行消费是不能进行购买请求的(和Google支付很相似),这里笔者刚开始被坑了很久,一直不能支付,主要还是没有认真看文档,哈哈哈。
  • 第五步:填写相关信息,进行购买请求。
  • 第六步:这里呢,主要是对onestore购买请求的回调处理。比如说拿到支付ID和订单号,上传到自己的服务器进行验证处理。如果验证通过了,服务器那边会和ONE store对接验证,然后,ONE store会进行发货处理,然后服务器给你一个反馈,然后,你再次调用消费处理,这样,一个完整的支付流程就完了。
  • 第七步:释放操作。
  • 2、说了那么多,估计你们看文字都看累了,下面,看代码。
public class OneStorePlayManager {

@SuppressLint("StaticFieldLeak")
 //支付客户端
private static PurchaseClient mPurchaseClient;
 //版本
private static final int IAP_API_VERSION = 5;
private static final String TAG = OneStorePlayManager.class.getSimpleName();
 //签名验证
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA512withRSA";
 //公钥
private static String mPublicKey;
  //是否初始化
private static boolean mIsInit = false;
private static final String PURCHASE_ID = "PURCHASE_ID";
private static final String DEVELOPER_PAYLOAD = "DEVELOPER_PAYLOAD";
//是否消费
private static boolean mISConsume = false;

private static void init(Context context, String publickey) {
    mPurchaseClient = new PurchaseClient(context, publickey);
    mPublicKey = publickey;
}


/**
 * 初始化
 *
 * @param context   上下文
 * @param mListener 回调
 */
public static void initOneStore(final Activity context, final String publickey,
                                final String productType,
                              
                                final onOneStoreSupportListener mListener) {
    if (!mIsInit) {
        init(context, publickey);
        mIsInit = true;
    }
    if (mPurchaseClient != null) {
        mPurchaseClient.connect(new PurchaseClient.ServiceConnectionListener() {
            @Override
            public void onConnected() {
                if (mListener != null) {
                    mListener.onOneStoreConnected();
                }
                checkBillingSupportedAndLoadPurchases(context, LTAppID, LTAppKey, testID, productType, mListener, mUploadListener);
            }

            @Override
            public void onDisconnected() {
                if (mListener != null) {
                    mListener.onOneStoreDisConnected();
                }
            }

            @Override
            public void onErrorNeedUpdateException() {//必须更新到最新的onestore客户端后才能进行支付
                if (mListener != null) {
                    mListener.onOneStoreFailed(OneStoreResult.RESULT_CONNECTED_NEED_UPDATE);
                    PurchaseClient.launchUpdateOrInstallFlow(context);
                }
            }
        });
    }
}

/**
 * 检查是否支持
 */
private static void checkBillingSupportedAndLoadPurchases(final Context context,
                                                          final String productType,
                                                          final onOneStoreSupportListener mListener) {
    if (mPurchaseClient == null) {
        if (mListener != null) {
            mListener.onOneStoreClientFailed("PurchaseClient is not initialized");
        }
    } else {
        mPurchaseClient.isBillingSupportedAsync(IAP_API_VERSION, new PurchaseClient.BillingSupportedListener() {
            @Override
            public void onSuccess() {
                mListener.onOneStoreSuccess(OneStoreResult.RESULT_BILLING_OK);
                // 然后通过对托管商品和每月采购历史记录的呼叫接收采购历史记录信息。
                //loadPurchases((Activity) context,  mListener);
                Log.e(TAG, "isBillingSupportedAsync : RESULT_BILLING_OK");
                mPurchaseClient.queryPurchasesAsync(IAP_API_VERSION, productType,
                        new PurchaseClient.QueryPurchaseListener() {
                            @Override
                            public void onSuccess(List<PurchaseData> purchaseDataList, String productType) {
                                Log.e(TAG, "queryPurchasesAsync onSuccess, " + purchaseDataList.toString());
                                for (PurchaseData purchase : purchaseDataList) {
                                    consumeItem((Activity) context, purchase, mListener);
                                }
                            }

                            @Override
                            public void onError(IapResult iapResult) {
                                mListener.onOneStoreError("onError====" + iapResult.toString());
                            }

                            @Override
                            public void onErrorRemoteException() {
                                mListener.onOneStoreFailed(OneStoreResult.RESULT_PURCHASES_REMOTE_ERROR);
                            }

                            @Override
                            public void onErrorSecurityException() {
                                mListener.onOneStoreFailed(OneStoreResult.RESULT_PURCHASES_SECURITY_ERROR);
                            }

                            @Override
                            public void onErrorNeedUpdateException() {
                                mListener.onOneStoreFailed(OneStoreResult.RESULT_PURCHASES_NEED_UPDATE);
                                PurchaseClient.launchUpdateOrInstallFlow((Activity) context);
                            }
                        });
            }

            @Override
            public void onError(IapResult iapResult) {
                mListener.onOneStoreError(iapResult.toString());
            }

            @Override
            public void onErrorRemoteException() {
                mListener.onOneStoreFailed(OneStoreResult.RESULT_BILLING_REMOTE_ERROR);
            }

            @Override
            public void onErrorSecurityException() {
                mListener.onOneStoreFailed(OneStoreResult.RESULT_BILLING_SECURITY_ERROR);
            }

            @Override
            public void onErrorNeedUpdateException() {
                mListener.onOneStoreFailed(OneStoreResult.RESULT_BILLING_NEED_UPDATE);
                PurchaseClient.launchUpdateOrInstallFlow((Activity) context);
            }
        });
    }
}


/**
 * 在管理商品 (inapp) 后或历史记录视图完成后, 消耗托管商品的消费.
 *
 * @param purchaseData 产品数据
 */
private static void consumeItem(final Activity context
                                final PurchaseData purchaseData,
                                final onOneStoreSupportListener mListener) {
    if (mPurchaseClient == null) {
        mListener.onOneStoreFailed(OneStoreResult.RESULT_PURCHASES_NEED_UPDATE);
        Log.e(TAG, "PurchaseClient is not initialized");
        return;
    }
    mPurchaseClient.consumeAsync(IAP_API_VERSION, purchaseData,
            new PurchaseClient.ConsumeListener() {
                @Override
                public void onSuccess(PurchaseData purchaseData) {
                    Log.e(TAG, "consumeAsync===success");
                    mListener.onOneStoreSuccess(OneStoreResult.RESULT_CONSUME_OK);
                    if (!TextUtils.isEmpty(PreferencesUtils.getString(context, PURCHASE_ID)) &&
                            !TextUtils.isEmpty(PreferencesUtils.getString(context, DEVELOPER_PAYLOAD))) {
                        //上传到服务器处理进行验单,防止漏单
                        uploadServer(xx,xx,xx);
                    } else if (mISConsume) {
                        uploadServer(xx,xx,xx);
                    }
                    mISConsume = false;
                }

                @Override
                public void onError(IapResult iapResult) {
                    mListener.onOneStoreError(iapResult.toString());
                    Log.e(TAG, "consumeAsync onError,  消费错误" + iapResult.toString());
                }

                @Override
                public void onErrorRemoteException() {
                    mListener.onOneStoreFailed(OneStoreResult.RESULT_CONSUME_REMOTE_ERROR);
                    Log.e(TAG, "consumeAsync onError,  消费连接失败");
                }

                @Override
                public void onErrorSecurityException() {
                    mListener.onOneStoreFailed(OneStoreResult.RESULT_CONSUME_SECURITY_ERROR);
                    Log.e(TAG, "consumeAsync onError,  消费应用状态异常下请求支付");
                }

                @Override
                public void onErrorNeedUpdateException() {
                    mListener.onOneStoreFailed(OneStoreResult.RESULT_CONSUME_NEED_UPDATE);
                    Log.e(TAG, "consumeAsync onError,  消费产品需要更新");
                }
            });
}


/**
 * oneStore回调
 *
 * @param requestCode     请求码
 * @param resultCode      结果码
 * @param selfRequestCode 自定义请求码
 */
public static void onActivityResult(Context context, int requestCode, int resultCode, Intent data, int selfRequestCode, final onOneStoreSupportListener mSupportListener) {
    if (requestCode == selfRequestCode)
        if (resultCode == Activity.RESULT_OK) {
            if (!mPurchaseClient.handlePurchaseData(data)) {
                Log.e(TAG, "onActivityResult handlePurchaseData false ");
            } else {
                String signature = data.getStringExtra("purchaseSignature");
                String purchaseData = data.getStringExtra("purchaseData");
                Gson gson = new Gson();
                PurchaseData mPurchaseData = gson.fromJson(purchaseData, PurchaseData.class);
                if (mPurchaseData != null) {//这边是保存一下订单,刚进入应用的时候进行相关处理
                    PreferencesUtils.putString(context, PURCHASE_ID, mPurchaseData.getPurchaseId());
                    PreferencesUtils.putString(context, DEVELOPER_PAYLOAD, mPurchaseData.getDeveloperPayload());
                    consumeItem((Activity) context,  mPurchaseData, mSupportListener);
                    Log.e(TAG, "onActivityResult handlePurchaseData true " + mPurchaseData.toString() + "==="
                            + signature);
                }
            }
        } else {
            Log.e(TAG, "onActivityResult user canceled");
        }
}

/**
 * 获得商品
 *
 * @param productId       商品ID
 * @param selfRequestCode 请求码
 * @param productName     产品名称
 * @param type 产品类型
 * @param onOneStoreSupportListener  是否支持的接口
 * @OnCreateOrderFailedListener 创建订单的接口
 */
public static void getProduct(final Activity context,
                              int selfRequestCode, String productName,
                              final String productId, String type,
                              final onOneStoreSupportListener mListener, final OnCreateOrderFailedListener mCreateListener) {
    if (!mISConsume) {
        if (!mIsInit) {
            init(context, mPublicKey);
        } else {
            getLTOrderID(context,  selfRequestCode, productName, productId, type,
                    mListener,
                    mCreateListener);

        }
    }
}

/**
 * 购买
 * @ devPayLoad 订单号(自己的服务器创建的)
 */
private static void launchPurchase(final Activity context,
                                   int selfRequestCode, String productName,
                                   final String productId, String type, final String devPayLoad,
                                   final onOneStoreSupportListener mListener) {
    if (mPurchaseClient != null) {
        mPurchaseClient.launchPurchaseFlowAsync(IAP_API_VERSION,
                context, selfRequestCode, productId, productName,
                type, devPayLoad, "",
                false, new PurchaseClient.PurchaseFlowListener() {

                    @Override
                    public void onSuccess(PurchaseData purchaseData) {
                        Log.e(TAG, "launchPurchaseFlowAsy======= " + purchaseData.getDeveloperPayload() + "====" + devPayLoad);

                    }

                    @Override
                    public void onError(IapResult result) {
                        Log.e(TAG, "launchPurchaseFlowAsync onError, " + result.toString());
                        mListener.onOneStoreError(result.toString());
                    }

                    @Override
                    public void onErrorRemoteException() {
                        Log.e(TAG, "launchPurchaseFlowAsync onError=====onErrorRemoteException");
                        mListener.onOneStoreFailed(OneStoreResult.RESULT_PURCHASES_FLOW_REMOTE_ERROR);
                    }

                    @Override
                    public void onErrorSecurityException() {
                        Log.e(TAG, "launchPurchaseFlowAsync onError=====onErrorSecurityException");
                        mListener.onOneStoreFailed(OneStoreResult.RESULT_PURCHASES_FLOW_SECURITY_ERROR);
                    }

                    @Override
                    public void onErrorNeedUpdateException() {
                        Log.e(TAG, "launchPurchaseFlowAsync onError=====onErrorNeedUpdateException");
                        mListener.onOneStoreFailed(OneStoreResult.RESULT_PURCHASES_FLOW_NEED_UPDATE);
                        PurchaseClient.launchUpdateOrInstallFlow(context);
                    }
                });
    }
}


/**
 * 创建订单
 *
 */
private static void getLTOrderID(final Activity activity, 
                                 final int selfRequestCode, final String productName,
                                 final String productId, final String type,
                                 final onOneStoreSupportListener mListener,
                                 final OnCreateOrderFailedListener mOrderListener) {
                    //从服务器获取订单号然后进行购买处理
                    launchPurchase(activity, selfRequestCode, productName, productId,
                            type, result, mListener);
                    mISConsume = true;
              
}

private static void uploadServer(final Context context, 
                                 String purchase_id, String devPayLoad) {

             //服务器验单成功后进行相关的操作,比如说清除没必要的缓存,等等这些操作
  
            mISConsume = false;
            if (!TextUtils.isEmpty(PreferencesUtils.getString(context, PURCHASE_ID))) {
                PreferencesUtils.remove(context, PURCHASE_ID);
            }
            if (!TextUtils.isEmpty(PreferencesUtils.getString(context, DEVELOPER_PAYLOAD))) {
                PreferencesUtils.remove(context, DEVELOPER_PAYLOAD);
            }
     
}

/**
 * 释放
 */
public static void release() {
    mISConsume = false;
    mIsInit = false;
    if (mPurchaseClient != null) {
        mPurchaseClient.terminate();
        mPurchaseClient = null;
    }

}

private static boolean verifyPurchase(String signedData, String signature) {
    if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(signature)) {
        return false;
    }
    PublicKey key = generatePublicKey(mPublicKey);
    return verify(key, signedData, signature);
}

private static PublicKey generatePublicKey(String encodedPublicKey) {
    try {
        byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
        return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
    } catch (NoSuchAlgorithmException e) {
        throw new SecurityException("RSA not available", e);
    } catch (InvalidKeySpecException e) {
        Log.e(TAG, "Invalid key specification.");
        throw new IllegalArgumentException(e);
    }
}

private static boolean verify(PublicKey publicKey, String signedData, String signature) {
    try {
        Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
        sig.initVerify(publicKey);
        sig.update(signedData.getBytes());
        if (!sig.verify(Base64.decode(signature, Base64.DEFAULT))) {
            Log.e(TAG, "Signature verification failed.");
            return false;
        }
        return true;
    } catch (NoSuchAlgorithmException e) {
        Log.e(TAG, "NoSuchAlgorithmException.");
    } catch (InvalidKeyException e) {
        Log.e(TAG, "Invalid key specification.");
    } catch (SignatureException e) {
        Log.e(TAG, "SignatureTest exception.");
    } catch (IllegalArgumentException e) {
        Log.e(TAG, "Base64 decoding failed.");
    }
    return false;
}


}
  • 是否支持支付的接口
/**
 * 是否支持支付的接口
 */
public interface onOneStoreSupportListener {

/**
 * oneStore连接失败
 *
 * @param failedMsg 失败信息
 */
void onOneStoreClientFailed(String failedMsg);

/**
 * oneStore支付失败
 *
 * @param result 失败信息
 */
void onOneStoreFailed(OneStoreResult result);

/**
 * oneStore支付出错
 *
 * @param result 错误信息
 */
void onOneStoreError(String result);

/**
 * oneStore支付成功
 *
 * @param result 成功信息
 */
void onOneStoreSuccess(OneStoreResult result);

/**
 * oneStore连接成功
 */
void onOneStoreConnected();

/**
 * oneStore未连接成功
 */
void onOneStoreDisConnected();

 }
  • 创建订单接口

    public interface OnCreateOrderFailedListener {
    
    
     void onCreateOrderFailed(String failed);
    
     void onCreateOrderError(String errorMsg);
     }
    

Screenshot_20190520-104328.jpg

如上图所示:只有点击close的时候才能支付成功,直接退出app是支付取消处理。

最后

补充一点,接入onestore支付的一般是面相韩国那边的app,并且大多数是游戏app,那么,就牵扯到了横屏。那么,onestore有没有横屏处理呢?当然了,在Manifest中配置就可以了。

image.png

注意:横屏的话用popup就可以了,不用选full,如果竖屏呢,写成full或者不写都是可以的。

好了,以上就是这次onestore的应用内开发,如果有什么不懂的地方,或者写的不好的地方,欢迎指正。当然,也可以加群(493180098)问我

感谢

OneStore_Android_接入
可以看看,但是讲的不是特别详细,还是要感谢上面那篇文章的作者。

感悟

通过这次接入ONE store的工作完成后,充分认识了自己。以前觉得百度上找不到文档,就感觉特别慌,有点无所适从。但是,通过这次ONE store的开发,觉得,也就那样。生活本来就是这样的,都是摸着石头过河。鲁迅先生还说过:世上本没有路,走的人多了便有了路。收拾好心情,继续努力吧,少年,哈哈哈哈。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342