【问题标题】:Android InAppBilling - what to do when user presses the buy button?Android InAppBilling - 当用户按下购买按钮时该怎么办?
【发布时间】:2012-06-19 11:08:48
【问题描述】:

我在本地设置了“Dungeons”InAppBilling 示例,并准备尝试一下,但我有点困惑。我有一个这样的按钮:

Button donate = (Button)findViewById(R.id.donate);     
donate.setOnClickListener(new Button.OnClickListener() {  
        public void onClick(View v) {     
        // But what do I do here? :)
        }
});

当它被调用时,我需要做什么才能真正进入 android 商店的支付屏幕?

谢谢!

【问题讨论】:

标签: android


【解决方案1】:

我最好建议您使用此代码,因为此示例一开始非常简单且易于处理..您要做的事情是

http://blog.blundell-apps.com/simple-inapp-billing-payment/

  1. 从以上链接下载示例项目代码(底部有代码说明和下载链接)
  2. 在您要在应用计费中实现的 android 项目中,创建包 com.android.vending.billing 并放置 IMarketBillingService.aidl(您可以在步骤 1 中下载的项目中找到此文件以及下面提到的所有文件)
  3. 将以下实用程序文件放在任何包中并相应地更正导入语句。

          * BillingHelper.java
          * BillingReceiver.java
          * BillingSecurity.java
          * BillingService.java
          * C.java
    
  4. 将公钥(您可以在编辑配置文件底部的开发人员控制台中找到)放在 BillingSecurity.java 中的行中,即 String base64EncodedPublicKey = "your public key here"

  5. 在您的清单中声明以下权限(在应用程序标签外)、服务和接收方(在应用程序标签内),如下所示(也可以参见代码中的清单以供参考)

     //outside the application tag 
     <uses-permission android:name="com.android.vending.BILLING" />
    
     // Inside the application tag
     <service android:name=".BillingService" />
    
    <receiver android:name=".BillingReceiver">
        <intent-filter>
            <action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
            <action android:name="com.android.vending.billing.RESPONSE_CODE" />
            <action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />            
        </intent-filter>
    </receiver> 
    
  6. 将下面提到的代码放置在您的活动中进行购买的地方。

    //at the starting of your onCreate()
    startService(new Intent(mContext, BillingService.class));
    BillingHelper.setCompletedHandler(mTransactionHandler);
    
    //outside onCreate() Within class
     public Handler mTransactionHandler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            Log.i(TAG, "Transaction complete");
            Log.i(TAG, "Transaction status: "+BillingHelper.latestPurchase.purchaseState);
            Log.i(TAG, "Item purchased is: "+BillingHelper.latestPurchase.productId);
    
            if(BillingHelper.latestPurchase.isPurchased()){
                //code here which is to be performed after successful purchase
            }
        };
    
     };
    
     //code to initiate a purchase... can be placed in onClickListener etc
      if(BillingHelper.isBillingSupported()){
            BillingHelper.requestPurchase(mContext, "android.test.purchased"); 
            // where android.test.purchased is test id for fake purchase, when you create products through developer console you can set a code to pass the id(which is given on developer console while creating a product) of the item which is selected for purchase to intiate purchase of that item.
        } else {
            Log.i(TAG,"Can't purchase on this device");
             // Do Anything Heer to show user that purchase not possible on this device
        }
    

注意:要进行测试购买,您需要将公钥放在 BillingSecurity.java 中,如上所述,其次您需要将 apk 上传到开发者控制台(您可以将其保留为 uuppublished 和 unactive),第三您需要一个真实的已更新 Play 商店应用的 android 设备(模拟器无法工作)。

注意:应用内购买所需的账户以及上述所有讨论中的描述不仅仅是简单的发布者账户,它的发布者账户嵌入了谷歌商家钱包账户。详情可以在下面的链接中找到。

http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=113468

【讨论】:

  • 我不确定 BillingHelper 类是什么。那是从哪里来的?此外,我仍然不确定在我的购买按钮监听器中放置什么确切的代码。感谢您的帮助。
  • BillingHelper 是旨在协助购买应用程序的自定义类,您可以在链接中找到它,如底部链接中提到的,包含该类的项目的下载链接。我还尝试在开发人员站点上遵循示例,但对于不熟悉完整结构的人来说,这会让人感到困惑,这就是为什么我向您推荐我尝试过的方法。我已经编写了完整的说明来遵循那个简单的例子,我没有回答按钮监听器的代码,但指导你使用我使用并成功实现的更简单的方法
【解决方案2】:

1)下载

http://developer.android.com/guide/google/play/billing/billing_integrate.html#billing-download

2)添加

http://developer.android.com/guide/google/play/billing/billing_integrate.html#billing-add-aidl

3) 在你的 android 清单文件中添加权限

http://developer.android.com/guide/google/play/billing/billing_integrate.html#billing-permission

现在您的项目应该如下所示...

4) 放置公钥(您可以在开发者控制台的 编辑配置文件的底部)在 Security.java 行中说 String base64EncodedPublicKey = "你的公钥在这里"

5) 最后你的带有按钮的活动应该是这样的

公共类 YourActivity 扩展 Activity 实现 OnClickListener { String issueProductId = "您的产品 ID";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.updates);
    SetInAppBilling();
    Button donate = (Button) findViewById(R.id.donate);
    donate.setOnClickListener(new Button.OnClickListener() {
        public void onClick(View v) {
            if (mBillingService.requestPurchase(issueProductId, null)) {

            } else {
                showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
                Log.i("tag", "Can't purchase on this device");

            }

        }
    });
}


public void register() {
    ResponseHandler.register(mDungeonsPurchaseObserver);
}

public void unregister() {
    ResponseHandler.unregister(mDungeonsPurchaseObserver);
}

public void close_unbind() {
    if (mPurchaseDatabase != null)
        // mPurchaseDatabase.close();
        if (mBillingService != null)
            mBillingService.unbind();
    // stopService(new Intent(this, BillingService.class));
}

/**
 * Called when this activity becomes visible.
 */
@Override
protected void onStart() {
    super.onStart();

    register();
}

/**
 * Called when this activity is no longer visible.
 */
@Override
protected void onStop() {
    unregister();
    super.onStop();

}

@Override
protected void onDestroy() {
    close_unbind();
    super.onDestroy();

}

private static final String TAG = "YourActivity";

private static final String DB_INITIALIZED = "db_initialized";

// private static final String Dir_Check = "Dir_Check";

private DungeonsPurchaseObserver mDungeonsPurchaseObserver;
private Handler mHandler;

private BillingService mBillingService;
private PurchaseDatabase mPurchaseDatabase;
private static final int DIALOG_CANNOT_CONNECT_ID = 1;
private static final int DIALOG_BILLING_NOT_SUPPORTED_ID = 2;
private Cursor mOwnedItemsCursor;

public void SetInAppBilling() {
    mHandler = new Handler();
    mDungeonsPurchaseObserver = new DungeonsPurchaseObserver(mHandler);
    mBillingService = new BillingService();
    mBillingService.setContext(this);

    mPurchaseDatabase = new PurchaseDatabase(this);

    mOwnedItemsCursor = mPurchaseDatabase
            .queryAllPurchasedHistroyTabelItems();
    startManagingCursor(mOwnedItemsCursor);

    SharedPreferences prefs = getPreferences(MODE_PRIVATE);
    boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
    // Check if billing is supported.
    ResponseHandler.register(mDungeonsPurchaseObserver);
    if (!mBillingService.checkBillingSupported()) {
        showDialog(DIALOG_CANNOT_CONNECT_ID);
    }
}

private class DungeonsPurchaseObserver extends PurchaseObserver {
    public DungeonsPurchaseObserver(Handler handler) {
        super(YourActiviy.this, handler);
    }

    @Override
    public void onBillingSupported(boolean supported) {

        Log.i(TAG, "supportedCheck: " + supported);
        if (Consts.DEBUG) {
            Log.i(TAG, "supported: " + supported);
        }
        if (supported) {
            restoreDatabase();
        } else {
            showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
        }
    }

    @Override
    public void onPurchaseStateChange(PurchaseState purchaseState,
            String itemId, int quantity, long purchaseTime,
            String developerPayload) {
        if (Consts.DEBUG) {
            Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " "
                    + purchaseState);
        }

        if (developerPayload == null) {

        } else {

        }

        Log.e(TAG, "onPurchaseStateChangeCheck: " + "onPurchaseStateChange");
        if (purchaseState == PurchaseState.PURCHASED) {

            /** TODO: */
            Toast.makeText(
                    mContext,
                    "You successfully upgraded to the entire Volume One. Enjoy!",
                    Toast.LENGTH_SHORT).show();
            finish();
        }

    }

    @Override
    public void onRequestPurchaseResponse(RequestPurchase request,
            ResponseCode responseCode) {
        if (Consts.DEBUG) {
            Log.d(TAG, request.mProductId + ": " + responseCode);
        }
        if (responseCode == ResponseCode.RESULT_OK) {
            if (Consts.DEBUG) {
                Log.i(TAG, "purchase was successfully sent to server");
            }

        } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
            if (Consts.DEBUG) {
                Log.i(TAG, "user canceled purchase");
            }

        } else {
            if (Consts.DEBUG) {
                Log.i(TAG, "purchase failed");
            }

        }
    }

    @Override
    public void onRestoreTransactionsResponse(RestoreTransactions request,
            ResponseCode responseCode) {
        if (responseCode == ResponseCode.RESULT_OK) {
            if (Consts.DEBUG) {
                Log.d(TAG, "completed RestoreTransactions request");
            }
            // Update the shared preferences so that we don't perform
            // a RestoreTransactions again.

            SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
            SharedPreferences.Editor edit = prefs.edit();
            edit.putBoolean(DB_INITIALIZED, true);
            edit.commit();

            mOwnedItemsCursor = mPurchaseDatabase
                    .queryAllPurchasedHistroyTabelItems();
            Log.d(TAG, String.valueOf(mOwnedItemsCursor.getCount()));
            startManagingCursor(mOwnedItemsCursor);

            if (mOwnedItemsCursor.getCount() > 0) {
                Log.d(TAG, "Updating the DB");
                Toast.makeText(
                        mContext,
                        "You successfully upgraded to the entire Volume One. Enjoy!",
                        Toast.LENGTH_SHORT).show();
                finish();
            }

        } else {
            if (Consts.DEBUG) {
                Log.d(TAG, "RestoreTransactions error: " + responseCode);
            }
        }
    }
}

@Override
protected Dialog onCreateDialog(int id) {
    switch (id) {
    case DIALOG_CANNOT_CONNECT_ID:
        return createDialog(R.string.cannot_connect_title,
                R.string.cannot_connect_message);
    case DIALOG_BILLING_NOT_SUPPORTED_ID:
        return createDialog(R.string.billing_not_supported_title,
                R.string.billing_not_supported_message);
    default:
        return null;
    }
}

private Dialog createDialog(int titleId, int messageId) {
    String helpUrl = replaceLanguageAndRegion(getString(R.string.help_url));
    if (Consts.DEBUG) {
        Log.i(TAG, helpUrl);
    }
    final Uri helpUri = Uri.parse(helpUrl);

    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle(titleId)
            .setIcon(android.R.drawable.stat_sys_warning)
            .setMessage(messageId)
            .setCancelable(false)
            .setPositiveButton(android.R.string.ok, null)
            .setNegativeButton(R.string.learn_more,
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog,
                                int which) {
                            Intent intent = new Intent(Intent.ACTION_VIEW,
                                    helpUri);
                            startActivity(intent);
                        }
                    });
    return builder.create();
}

/**
 * Replaces the language and/or country of the device into the given string.
 * The pattern "%lang%" will be replaced by the device's language code and
 * the pattern "%region%" will be replaced with the device's country code.
 * 
 * @param str
 *            the string to replace the language/country within
 * @return a string containing the local language and region codes
 */
private String replaceLanguageAndRegion(String str) {
    // Substitute language and or region if present in string
    if (str.contains("%lang%") || str.contains("%region%")) {
        Locale locale = Locale.getDefault();
        str = str.replace("%lang%", locale.getLanguage().toLowerCase());
        str = str.replace("%region%", locale.getCountry().toLowerCase());
    }
    return str;
}

private void restoreDatabase() {
    SharedPreferences prefs = getPreferences(MODE_PRIVATE);
    boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
    if (!initialized) {
        mBillingService.restoreTransactions();
        // Toast.makeText(this, "restoring...", Toast.LENGTH_LONG).show();

    }
}

}

【讨论】:

  • @M Mohsin Naeem 你给了很好的建议。我有一个问题我现在无法解决。在这一行: mBillingService.requestPurchase(issueProductIdDonate, null) 我收到一个编译错误,BillingService 类型中的方法 requestPurchase(String, String, String) 不适用于参数 (String, null)
  • 在这个方法的实现中,我删除了它正上方的@overrides 字符串——我不确定是否需要它。
  • @Override 是必需的。你需要写这个。你所面临的错误不应该出现。确保您使用的是来自 Goole play 的最新代码。同时在“BillingService.java”中检查您是否拥有这段代码。 'public boolean requestPurchase(String productId, String developerPayload) { return new RequestPurchase(productId, developerPayload).runRequest();' } 如果您仍然有问题,请分享您的代码。
【解决方案3】:

有两种方法

  • 在您的服务器端维护一个数据库,并为用户+购买的产品列表+到期日期创建一个表
  • 或客户端应用程序将加密代码保存在共享首选项中(这样就不会轻易被黑客入侵)

Android API 不为您提供任何库存 API 来维护您的购买。

我使用了第二个选项。

【讨论】:

  • 谢谢 - 这些是我的想法,但想知道是否有其他方法可以做到这一点。这有帮助。您是否还知道在我的原始问题中将什么代码放入我的按钮侦听器中,以便以正确的购买意图将用户发送到商店?
  • 之前回答过类似的问题,参考一下。希望你能找到你的答案。 stackoverflow.com/questions/11193754/…
【解决方案4】:

--单品采购 store.purchase({"android.test.purchased"})

-- multi-item purchase
store.purchase( { "android.test.purchased", "android.test.canceled" } )

参考Getting Started with Android In-app Billing

此代码可能是您正在寻找的代码

【讨论】:

  • 谢谢 - 如何让 store 对象执行 store.purchase 调用?什么是“android.test.canceled”?谢谢:)
【解决方案5】:

这里的代码我用于Simple InApp Billing / Payment,你也可以使用。

另请参阅this code 可能会有所帮助。

【讨论】:

    【解决方案6】:

    来自 Google 的示例应用内结算代码是一个良好的开端。我已经发布了一个指向我给出的教程的链接,该教程分为三个部分。此链接是三个中的第一个。希望对你有帮助。

    http://www.mobileoped.com/2012/04/06/android-google-play-in-app-billing-part-1/

    【讨论】:

    • 顺便说一句,这是一个很棒的教程。我现在正在经历它,喜欢你的写作风格。我感到困惑的一件事是,用户单击“购买”的位置是一个独立的活动,与您拥有 handler = new Handler(); 代码的位置不同。 ....appPurchaseObserver = new AppPurchaseObserver(handler); ....或者它在同一个活动中?我认为它发生在用户单击 puechase 时的鼠标单击事件中。
    猜你喜欢
    • 2015-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多