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

前提:

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

首先

  • 1、查看文档
    要做ONE store集成开发,第一步当然是查看官方文档了,我这里集成的是最新的版本,也就是v5版本。也有之前的v3和v4等版本,但是,人要向前看不是。所以,就集成了最新的版本了。

onestore官方文档
onestore官方文档集成

  • 2、下载ONE store的jar包(在如下图所示的地方下载对应的开发包和官方demo)
    题外话:感觉官方的demo写的真的不咋地,还没有文档写的清楚,并且让人看得云里雾里的。
    image

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

jar下载地址

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

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

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

+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;
}

}

image

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

最后

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

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

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

感谢

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

感悟

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