Google Play Billing (谷歌商店内支付)

访客 116 0

引用自:http://blog.csdn.net/xyxjn/article/details/50331529

  1. 如billing开发文档所说,要在你的应用中实现In-app Billing只需要完成以下几步就可以了。  

Google Play Billing (谷歌商店内支付)-第1张图片-谷歌商店上架

[html]  view plain  copy
  1. 第一,把你上篇下载的AIDL文件添加到你的工程里,第二,把  
<uses-permission android:name="com.android.vending.BILLING" />

这个权限加到你工程的AndroidManifest.xml文件中,第三,创建一个ServiceConnection,并把它绑定到IInAppBillingService中。完成上面三条后就可以使用支付了。当然这只是一个简单的介绍。其实Google的这个支付,大部分都是你手机上的Google Play来进行处理的,你只需要处理购买请求,处理购买结果就行了。文档写的很好,先把这个文档看完,就知道支付流程了。

 

正文:

1.内购商品相关

对于我的项目而言,我们在Google后台设置的是可管理消耗型商品(managed per user account),具体来说就是游戏中的水晶。玩家可以多次购买这种商品。然而,Google后台还提供了另一种只能购买一次的订阅型商品(subscription)。购买成功后,使用这个商品需要主动向Google Play发送消耗请求,并等待消耗成功后才能再次下单购买。因此,在游戏支付过程中会增加一个操作步骤,即请求消耗已成功购买的商品。

2. 检查设备是否兼容Google Play服务。

在进行支付之前,Google billing会先检查您的手机是否支持Google billing。为了提供更好的用户体验,建议在进行Google billing检测之前,先检测用户设备是否安装了Google Play应用商店和Google Play Service。如果用户设备未满足这两个基本要求,可以弹出提示引导用户去安装。有两种方式可以实现这一功能:一种是通过使用上篇下载的Service扩展包中的Google Play Service进行检测;另一种是编写代码来遍历设备上已安装的应用程序,并检查是否存在安装了Google Play。首先我们来介绍第一种方式。

(1) Google Play服务

在上次下载的Service包中,将会包含一个库工程。

Google Play Billing (谷歌商店内支付)-第2张图片-谷歌商店上架

将这个库工程导入你的eclipse,并引用到你的工程中即可轻松使用。具体操作请参考docs文件夹下的文档,非常简单!成功导入后,只需调用其中一个方法即可完成任务。

/**     * Check the device to make sure it has the Google Play Services APK.If     * it doesn‘t, display a dialog that allows users to download the APK from     * the Google Play Store or enable it in the device‘s system settings     */    private boolean checkPlayServices()    {        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);        if(resultCode != ConnectionResult.SUCCESS)        {            if(GooglePlayServicesUtil.isUserRecoverableError(resultCode))            {                GooglePlayServicesUtil.getErrorDialog(resultCode, this,                        PLAY_SERVICES_RESOLUTION_REQUEST).show();            }            else            {                Log.i(TAG, "This device is not supported");                finish();            }            return false;        }        return true;    }

如果当前设备的Google Service不可用,就会弹出提示,引导用户去设置安装。如果此设备不支持的话,就也不需要检测Google billing是否可用了。多说一句,Google Play Service可以做很多事的,如果觉得只用上面的功能太简单的话,就可以考虑把应用自动更新也加上,当你在Google Play上传了新版程序后,Google Play会帮你提示用户更新程序。还有一个比较好玩的就是如果引入了这个库工程后,就可以加GCM了(Google Cloud Messaging),就是消息推送推送功能,当然这个比较麻烦,有兴趣的可以去加加看。

 

(2)遍历包名

在设备上运行的Google Play服务的包名是com.google.android.gms,而Google Play应用程序的包名是com.Android.vending。您可以在应用程序启动时遍历设备上的包名,并引导用户安装这两个组件,如果它们不存在。

遍历包名方法

//Check Google Play    protected boolean isHaveGooglePlay(Context context, String packageName)    {        //Get PackageManager        final PackageManager packageManager = context.getPackageManager();        //Get The All Install App Package Name        List<PackageInfo> pInfo = packageManager.getInstalledPackages(0);                //Create Name List        List<String> pName = new ArrayList<String>();                //Add Package Name into Name List        if(pInfo != null){            for(int i=0; i<pInfo.size(); i++){                String pn = pInfo.get(i).packageName;                pName.add(pn);                                //Log.v("Package Name", "PackAgeName: = " + pn);            }        }                //Check         return pName.contains(packageName);    }

提示安装方法

Uri uri = Uri.parse(market://details?id= + 要安装程序的包名);
Intent it = new Intent(Intent.ACTION_VIEW, uri); 

 

然而,我仍然建议使用Google Play服务进行检测。第二种方法似乎不太可行,因为即使某些用户安装了Google Play(例如国内用户),他们也无法支持Google Play服务。

第三步是添加代码(终于可以加入支付代码了)。

将上一篇下载的样本中的util代码全部复制到您的项目中,您可以新建一个包并将其放入其中。

Google Play Billing (谷歌商店内支付)-第3张图片-谷歌商店上架

这个说明一下,其实这个例子的代码还是不错的,本着天下代码一大抄和拿来主义,就直接拿来用吧!当然如果你觉得这个代码写的不好,或者不适用你的工程,你就可以依据文档自己写适用的代码。当然文档里说过,为了防止别人破解你的游戏,最好把里面的变量和方法都改下名字,毕竟这里的代码任何人都看得到。我的做法是照搬过来了,只是把IabHelper.Java改造了下,因为这个是整个支付的关键,其他都是辅助的,可以不管。

把这里的代码拷完,把该import的都import了,你就可以照samples中的代码开写自己的支付了。针对单机游戏,就需要考虑这个代码改造和本地的验证,加密了。针对网络游戏就要简单了。因为我其实对java不太熟悉Google Play Billing (谷歌商店内支付)-第4张图片-谷歌商店上架,所以单机的加密,base验证,混淆什么的就不做介绍了。下面主要说网络游戏。

(1) IabHelper.java

这是支付的核心代码,其中已经包含了设置账单、商品查询、商品购买、商品回调以及商品验证和回调方法的编写。你只需参考示例即可使用。

01. 开启计费功能

就是前面提到的将ServiceConnection绑定到IInAppBillingService。这个功能非常完善,包括成功和失败时都会有相应的回调,还能处理各种异常情况。在你的程序启动的Activity中检测完设备是否安装了Google Play服务后,你可以创建一个IabHelper对象,并调用其中的方法来根据不同回调进行相应处理。

/**     * Starts the setup process. This will start up the setup process asynchronously.     * You will be notified through the listener when the setup process is complete.     * This method is safe to call from a UI thread.     *     * @param listener The listener to notify when the setup process is complete.     */    public void startSetup(final OnIabSetupFinishedListener listener) {        // If already set up, can‘t do it again.        checkNotDisposed();        if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");        // Connection to IAB service        logDebug("Starting in-app billing setup.");        mServiceConn = new ServiceConnection() {            @Override            public void onServiceDisconnected(ComponentName name) {                logDebug("Billing service disconnected.");                mService = null;            }            @Override            public void onServiceConnected(ComponentName name, IBinder service) {                if (mDisposed) return;                logDebug("Billing service connected.");                mService = IInAppBillingService.Stub.asInterface(service);                String packageName = mContext.getPackageName();                try {                    logDebug("Checking for in-app billing 3 support.");                    // check for in-app billing v3 support                    int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);                    if (response != BILLING_RESPONSE_RESULT_OK) {                        if (listener != null) listener.onIabSetupFinished(new IabResult(response,                                "Error checking for billing v3 support."));                        // if in-app purchases aren‘t supported, neither are subscriptions.                        mSubscriptionsSupported = false;                        return;                    }                    logDebug("In-app billing version 3 supported for " + packageName);                    // check for v3 subscriptions support                    response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);                    if (response == BILLING_RESPONSE_RESULT_OK) {                        logDebug("Subscriptions AVAILABLE.");                        mSubscriptionsSupported = true;                    }                    else {                        logDebug("Subscriptions NOT AVAILABLE. Response: " + response);                    }                    mSetupDone = true;                }                catch (RemoteException e) {                    if (listener != null) {                        listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,                                                    "RemoteException while setting up in-app billing."));                    }                    e.printStackTrace();                    return;                }                if (listener != null) {                    listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));                }            }        };        Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");        serviceIntent.setPackage("com.android.vending");        if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {            // service available to handle that Intent            mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);        }        else {            // no service available to handle that Intent            if (listener != null) {                listener.onIabSetupFinished(                        new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,                        "Billing service unavailable on device."));            }        }    }
[html]  view plain  copy
  1. samples中的代码  
// Create the helper, passing it our context and the public key to verify signatures with        Log.d(TAG, "Creating IAB helper.");        mHelper = new IabHelper(this, base64EncodedPublicKey);        // enable debug logging (for a production application, you should set this to false).        mHelper.enableDebugLogging(true);        // Start setup. This is asynchronous and the specified listener        // will be called once setup completes.        Log.d(TAG, "Starting setup.");        mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {            public void onIabSetupFinished(IabResult result) {                Log.d(TAG, "Setup finished.");                if (!result.isSuccess()) {                    // Oh noes, there was a problem.                    complain("Problem setting up in-app billing: " + result);                    return;                }                // Have we been disposed of in the meantime? If so, quit.                if (mHelper == null) return;                // IAB is fully set up. Now, let‘s get an inventory of stuff we own.                Log.d(TAG, "Setup successful. Querying inventory.");                mHelper.queryInventoryAsync(mGotInventoryListener);            }        });    }

 

02.查询商品

在setup方法的末尾,最后一个部分是

mHelper.queryInventoryAsync(mGotInventoryListener); 是用来查询您当前拥有的商品的。回调代码如下:   
// This is a listener that is called when we finish querying the items and subscriptions we own.
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
    public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
        Log.d(TAG, Query inventory finished.);
        // Have we been disposed of in the meantime? If so, quit.
        if (mHelper == null) return;
        // Is it a failure?
        if (result.isFailure()) {
            complain(Failed to query inventory:  + result);
            return;
        }
        Log.d(TAG, Query inventory was successful.);
        
       /* Check for items we own. Notice that for each purchase, we check
         * the developer payload to see if it's correct! See verifyDeveloperPayload().
         */
        
       // Do we have the premium upgrade?
       Purchase premiumPurchase = inventory.getPurchase(SKU_PREMIUM);
       mIsPremium = (premiumPurchase != null && ...   
检查是否有燃气供应 - 如果我们拥有燃气,应立即加满油箱。
购买燃气
购买 = inventory.getPurchase(SKU_GAS);
如果 (购买 != null && verifyDeveloperPayload(购买)) {
    Log.d(TAG, 我们有燃气。正在消耗它。);
    mHelper.consumeAsync(inventory.getPurchase(SKU_GAS), mConsumeFinishedListener);
    return;
} 

 

02.消耗商品

商品的消耗会在两个地方显现:首先是在所查询的商品中可能存在漏单,其次是每次成功购买后你所消耗的商品。此外,消耗商品还有一个回调函数如下所示:

// This method is called when consumption is complete.    IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {        public void onConsumeFinished(Purchase purchase, IabResult result) {            Log.d(TAG,"Consumption finished. Purchase: "+purcha   se+n>, result:"+r esult);            // If we were disposed of in the meantime , quit.           if (mHelper == null) return;            // We know this is the "gas" sku because it's the only one we consume,
             // so we don't check which sku was consumed. If you have more than one
             // sku,you probably should check... 

 

03.购买商品

按重要程度,购买商品应该排在第一位的,只是按支付流程走的话,购买商品却不是第一位,这里就根据支付流程来走吧。

 /**     * Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,     * which will involve bringing up the Google Play screen. The calling activity will be paused while     * the user interacts with Google Play, and the result will be delivered via the activity‘s     * {@link android.app.Activity#onActivityResult} method, at which point you must call     * this object‘s {@link #handleActivityResult} method to continue the purchase flow. This method     * MUST be called from the UI thread of the Activity.     *     * @param act The calling activity.     * @param sku The sku of the item to purchase.     * @param itemType indicates if it‘s a product or a subscription (ITEM_TYPE_INAPP or ITEM_TYPE_SUBS)     * @param requestCode A request code (to differentiate from other responses --     *     as in {@link android.app.Activity#startActivityForResult}).     * @param listener The listener to notify when the purchase process finishes     * @param extraData Extra data (developer payload), which will be returned with the purchase data     *     when the purchase completes. This extra data will be permanently bound to that purchase     *     and will always be returned when the purchase is queried.     */    public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode,                        OnIabPurchaseFinishedListener listener, String extraData) {        checkNotDisposed();        checkSetupDone("launchPurchaseFlow");        flagStartAsync("launchPurchaseFlow");        IabResult result;        if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {            IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,                    "Subscriptions are not available.");            flagEndAsync();            if (listener != null) listener.onIabPurchaseFinished(r, null);            return;        }        try {            logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);            Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);            int response = getResponseCodeFromBundle(buyIntentBundle);            if (response != BILLING_RESPONSE_RESULT_OK) {                logError("Unable to buy item, Error response: " + getResponseDesc(response));                flagEndAsync();                result = new IabResult(response, "Unable to buy item");                if (listener != null) listener.onIabPurchaseFinished(result, null);                return;            }            PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);            logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);            mRequestCode = requestCode;            mPurchaseListener = listener;            mPurchasingItemType = itemType;            act.startIntentSenderForResult(pendingIntent.getIntentSender(),                                           requestCode, new Intent(),                                           Integer.valueOf(0), Integer.valueOf(0),                                           Integer.valueOf(0));        }        catch (SendIntentException e) {            logError("SendIntentException while launching purchase flow for sku " + sku);            e.printStackTrace();            flagEndAsync();            result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");            if (listener != null) listener.onIabPurchaseFinished(result, null);        }        catch (RemoteException e) {            logError("RemoteException while launching purchase flow for sku " + sku);            e.printStackTrace();            flagEndAsync();            result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");            if (listener != null) listener.onIabPurchaseFinished(result, null);        }    }

以上是IabHelper中的支付购买代码,其中包括了重复购买商品类型和一次购买商品类型的处理。主要的代码是try里面的这一块

尝试构建购买意图,商品类型为 {itemType} 的 {sku}。获取购买意图包并检索响应码。如果响应码不是 BILLING_RESPONSE_RESULT_OK,则记录错误信息并结束异步操作,并返回无法购买商品的结果。如果监听器不为空,则调用 onIabPurchaseFinished 方法通知监听器。获取待处理的意图发送者,并启动购买意图,请求代码为 {requestCode}。设置请求代码、购买监听器和正在购买的商品类型为当前值。 

第三个参数是订单号。如果您的本地系统有支付服务器,可以由支付服务器生成订单号,并传递给客户端。这样一来,本地服务器也能够记录订单信息,以便于将来的查询和操作。建议使用时间戳加上商品名称和价格的格式作为订单号,这样可以更容易地查看订单信息。该订单号将会传递给Google,在购买成功后Google会原封不动地将其传递给您,因此您也可以在其中添加标识信息以进行比对操作。

其次,在成功调用getBuyIntent后,返回的Bundle中将包含一个表示成功的BILLING_RESPONSE_RESULT_OK返回码。接下来,可以使用该Bundle获取一个PendingIntent,具体代码示例如上所述。

三,进行付款

act.startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, new Intent(), 0, 0) 

 

四、支付已完成

 

 /**     * Handles an activity result that‘s part of the purchase flow in in-app billing. If you     * are calling {@link #launchPurchaseFlow}, then you must call this method from your     * Activity‘s {@link [email protected]} method. This method     * MUST be called from the UI thread of the Activity.     *     * @param requestCode The requestCode as you received it.     * @param resultCode The resultCode as you received it.     * @param data The data (Intent) as you received it.     * @return Returns true if the result was related to a purchase flow and was handled;     *     false if the result was not related to a purchase, in which case you should     *     handle it normally.     */    public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {        IabResult result;        if (requestCode != mRequestCode) return false;        checkNotDisposed();        checkSetupDone("handleActivityResult");        // end of async purchase operation that started on launchPurchaseFlow        flagEndAsync();        if (data == null) {            logError("Null data in IAB activity result.");            result = new IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result");            if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);            return true;        }        int responseCode = getResponseCodeFromIntent(data);        String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);        String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);        if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) {            logDebug("Successful resultcode from purchase activity.");            logDebug("Purchase data: " + purchaseData);            logDebug("Data signature: " + dataSignature);            logDebug("Extras: " + data.getExtras());            logDebug("Expected item type: " + mPurchasingItemType);            if (purchaseData == null || dataSignature == null) {                logError("BUG: either purchaseData or dataSignature is null.");                logDebug("Extras: " + data.getExtras().toString());                result = new IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature");                if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);                return true;            }            Purchase purchase = null;            try {                purchase = new Purchase(mPurchasingItemType, purchaseData, dataSignature);                String sku = purchase.getSku();                // Verify signature                if (!Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) {                    logError("Purchase signature verification FAILED for sku " + sku);                    result = new IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku " + sku);                    if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, purchase);                    return true;                }                logDebug("Purchase signature successfully verified.");            }            catch (JSONException e) {                logError("Failed to parse purchase data.");                e.printStackTrace();                result = new IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data.");                if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);                return true;            }            if (mPurchaseListener != null) {                mPurchaseListener.onIabPurchaseFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase);            }        }        else if (resultCode == Activity.RESULT_OK) {            // result code was OK, but in-app billing response was not OK.            logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode));            if (mPurchaseListener != null) {                result = new IabResult(responseCode, "Problem purchashing item.");                mPurchaseListener.onIabPurchaseFinished(result, null);            }        }        else if (resultCode == Activity.RESULT_CANCELED) {            logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));            result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");            if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);        }        else {            logError("Purchase failed. Result code: " + Integer.toString(resultCode)                    + ". Response: " + getResponseDesc(responseCode));            result = new IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response.");            if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);        }        return true;    }    public Inventory queryInventory(boolean querySkuDetails, List<String> moreSkus) throws IabException {        return queryInventory(querySkuDetails, moreSkus, null);    }

支付结果返回后会调用上面这个方法,对于支付失败和其中的错误,代码写的很清楚,可以自行处理。现在来关注支付成功后的结果验证。在上面方法中会从支付结果的数据中取得两个json数据。

        int responseCode = getResponseCodeFromIntent(data);        String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);        String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);

就是purchaseData和dataSignature。验证支付就是需要这两个参数和publicKey,例子里的验证方法是写在Security.java里的。里面写了三个方法来完成支付结果的验证。

 

对于游戏中存在本地支付服务器的情况,可以将该操作放在服务端处理,客户端只需将purchaseData和dataSignature传递给支付服务器。然后支付服务器会将验证结果返回给客户端,并进行相应的成功或失败处理。在成功处理后,还需要执行商品消耗操作。对于没有支付服务器的游戏而言,我认为实现安全的本地操作相对较困难。然而,即使通过服务器验证支付结果也存在一定风险,只是风险程度较低。

/**     * Verifies that the data was signed with the given signature, and returns     * the verified purchase. The data is in JSON format and signed     * with a private key. The data also contains the {@link PurchaseState}     * and product ID of the purchase.     * @param base64PublicKey the base64-encoded public key to use for verifying.     * @param signedData the signed JSON string (signed, not encrypted)     * @param signature the signature for the data, signed with the private key     */    public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {        if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||                TextUtils.isEmpty(signature)) {            Log.e(TAG, "Purchase verification failed: missing data.");            return false;        }        PublicKey key = Security.generatePublicKey(base64PublicKey);        return Security.verify(key, signedData, signature);    }    /**     * Generates a PublicKey instance from a string containing the     * Base64-encoded public key.     *     * @param encodedPublicKey Base64-encoded public key     * @throws IllegalArgumentException if encodedPublicKey is invalid     */    public static PublicKey generatePublicKey(String encodedPublicKey) {        try {            byte[] decodedKey = Base64.decode(encodedPublicKey);            KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);            return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));        } catch (NoSuchAlgorithmException e) {            throw new RuntimeException(e);        } catch (InvalidKeySpecException e) {            Log.e(TAG, "Invalid key specification.");            throw new IllegalArgumentException(e);        } catch (Base64DecoderException e) {            Log.e(TAG, "Base64 decoding failed.");            throw new IllegalArgumentException(e);        }    }    /**     * Verifies that the signature from the server matches the computed     * signature on the data.  Returns true if the data is correctly signed.     *     * @param publicKey public key associated with the developer account     * @param signedData signed data from server     * @param signature server signature     * @return true if the data and signature match     */    public static boolean verify(PublicKey publicKey, String signedData, String signature) {        Signature sig;        try {            sig = Signature.getInstance(SIGNATURE_ALGORITHM);            sig.initVerify(publicKey);            sig.update(signedData.getBytes());            if (!sig.verify(Base64.decode(signature))) {                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, "Signature exception.");        } catch (Base64DecoderException e) {            Log.e(TAG, "Base64 decoding failed.");        }        return false;    }

PublicKey:

这个公钥是用于验证支付结果的,因此它是一个绝对保密的关键信息,不能泄露给他人。请将该密钥存放在支付服务器端,而不要本地保存。

samples里的这段代码写的很有意思,能看出笑点不?

对于单机游戏而言,应该考虑将关键信息存储在安全的位置,并进行加密处理,避免直接写入代码中。因为如果没有自己的服务器来验证支付结果,无论用户如何操作,在本地环境下都很容易被破解。特别是对于销量较大的游戏而言,建议开发者自行搭建支付服务器端来验证支付结果。

/* base64EncodedPublicKey should be the public key specific to your application, obtained from the Google Play developer console. This is not your developer public key, but rather the app-specific public key.

Instead of directly embedding the entire literal string in the program, it is recommended to construct the key at runtime using different pieces or employ bit manipulation techniques (such as XOR with another string) to obfuscate the actual key. While the key itself is not considered secret information, we want to prevent attackers from easily replacing it with their own and forging messages from the server.

String base64EncodedPublicKey = CONSTRUCT_YOUR_KEY_AND_PLACE_IT_HERE; // Some sanity checks are performed here to ensure that you (the developer) have followed these guidelines */ 

 

为了验证支付结果,本地服务器可以使用publicKey进行签名验证,并且还可以通过与Google后台的交互通信来获取支付结果的验证。您可以参考以下文档进行操作。

Google Play Billing (谷歌商店内支付)-第5张图片-谷歌商店上架

然而,对于国内的开发者来说,在Google日益受到封锁加重的情况下,与Google服务器进行通信绝对会面临障碍。由于通信受阻可能导致验证失败,因此这个功能是可选的。如果你有兴趣,可以考虑添加它。

附加1:

如果直接使用samples的代码,需要注意以下几点。首先,将错误提示改为用户友好型。因为samples的错误提示主要面向开发者,所以它们通常非常详细。但对于用户来说,并不需要如此详细的信息。你只需告知用户操作成功、失败以及简单的失败原因即可。其次,在发布正式版时,请关闭打印信息输出。最后,请修改类名和变量名。

附加2:

如果在测试支付时遇到一下错误,可做的处理。

1. 当前应用程序不支持购买此商品:请确保您手机上安装的应用程序包名和签名与后台上传的一致。另外,请注意,上传到后台后,APK需要一段时间才能生效。

2. 商品购买失败:请确保您的代码中所使用的商品名称与后台一致。如果一致,请耐心等待一到两个小时后再进行测试,这可能是由于Google后台出现问题所导致的。

加载时间很长,最终出现未知错误提示。不用担心,这是由于Google后台的问题造成的。稍等片刻后再进行测试。

最终,国内开发者必须确保在VPN环境下进行测试!

附言:

以上就是Google In-app Billing的代码添加了,其实就是把samples讲了一下Google Play Billing (谷歌商店内支付)-第4张图片-谷歌商店上架,所以还是推荐去看下官方文档和samples吧,在那里你会学到更多。


标签: 谷歌商店内 谷歌商店上架 代码

发表评论 (已有0条评论)

还木有评论哦,快来抢沙发吧~