私はGoogle In-app Purchase V3
を実装しており、 ここ および公式ドキュメント ここ に記載されているすべての手順に従いました。 Alpha Testing
のアプリをGooglePlaystoreにアップロードし、それをPlaystore URLから実際のデバイスにダウンロードしましたが、エラーが発生します
エラー
認証が必要です。 Googleアカウントにサインインする必要があります。
アプリ内購入の私のコードはここにあります:
public class BuyPointsFragment extends Fragment
//In app Billing variable start
// Debug tag, for logging
static final String TAG = "com.myApp";
// Does the user have the premium upgrade?
boolean mIsPremium = false;
// Does the user have an active subscription to the infinite gas plan?
boolean mSubscribedToInfiniteGas = false;
// SKUs for our products: the premium upgrade (non-consumable) and gas
// (consumable)
static final String SKU_PREMIUM = "premium";
static final String SKU_GAS = "gas";
// SKU for our subscription (infinite gas)
static final String SKU_INFINITE_GAS = "infinite_gas";
// (arbitrary) request code for the purchase flow
static final int RC_REQUEST = 10001;
// Graphics for the gas gauge
static int[] TANK_RES_IDS = {};
// How many units (1/4 tank is our unit) fill in the tank.
static final int TANK_MAX = 4;
// Current amount of gas in tank, in units
int mTank;
// The helper object
IabHelper mHelper;
//In app billing variable end
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//inapp load game data
loadData();
String base64EncodedPublicKey = "Base64Key from publisher account";
// Some sanity checks to see if the developer (that's you!) really
// followed the
// instructions to run this sample (don't put these checks on your app!)
if (base64EncodedPublicKey.contains("CONSTRUCT_YOUR")) {
throw new RuntimeException(
"Please put your app's public key in MainActivity.Java. See README.");
}
if (getActivity().getPackageName().startsWith("com.myApp.activity")) {
throw new RuntimeException(
"Please change the sample's package name! See README.");
}
// Create the helper, passing it our context and the public key to
// verify signatures with
Log.d(TAG, "Creating IAB helper.");
mHelper = new IabHelper(getActivity(), 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(getString(R.string.problem_setting_inapp_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);
}
});
//In app billing code end here
}
//アプリ内の請求方法はここから始まります
public void inappCall(){
setWaitScreen(true);
Log.d(TAG, "Launching purchase flow for gas.");
/*
* TODO: for security, generate your payload here for verification. See
* the comments on verifyDeveloperPayload() for more info. Since this is
* a SAMPLE, we just use an empty string, but on a production app you
* should carefully generate this.
*/
String payload = "";
mHelper.launchPurchaseFlow(getActivity(), SKU_GAS, RC_REQUEST,
mPurchaseFinishedListener, payload);
}
// updates UI to reflect model
public void updateUi() {
// update the car color to reflect premium status or lack thereof
// ((ImageView)findViewById(R.id.free_or_premium)).setImageResource(mIsPremium
// ? R.drawable.premium : R.drawable.free);
// "Upgrade" button is only visible if the user is not premium
// findViewById(R.id.upgrade_button).setVisibility(mIsPremium ?
// View.GONE : View.VISIBLE);
// "Get infinite gas" button is only visible if the user is not
// subscribed yet
// (R.id.infinite_gas_button).setVisibility(mSubscribedToInfiniteGas ?
// View.GONE : View.VISIBLE);
// update gas gauge to reflect tank status
if (mSubscribedToInfiniteGas) {
// ((ImageView)findViewById(R.id.gas_gauge)).setImageResource(R.drawable.gas_inf);
} else {
int index = mTank >= TANK_RES_IDS.length ? TANK_RES_IDS.length - 1
: mTank;
// ((ImageView)findViewById(R.id.gas_gauge)).setImageResource(TANK_RES_IDS[index]);
}
}
// Enables or disables the "please wait" screen.
void setWaitScreen(boolean set) {
// findViewById(R.id.screen_main).setVisibility(set ? View.GONE :
// View.VISIBLE);
// findViewById(R.id.screen_wait).setVisibility(set ? View.VISIBLE :
// View.GONE);
}
void complain(String message) {
Log.e(TAG, "**** TrivialDrive Error: " + message);
alert("Error: " + message);
}
void alert(String message) {
AlertDialog.Builder bld = new AlertDialog.Builder(getActivity());
bld.setMessage(message);
bld.setNeutralButton("OK", null);
Log.d(TAG, "Showing alert dialog: " + message);
bld.create().show();
}
void saveData() {
/*
* WARNING: on a real application, we recommend you save data in a
* secure way to prevent tampering. For simplicity in this sample, we
* simply store the data using a SharedPreferences.
*/
SharedPreferences.Editor spe = getActivity().getPreferences(getActivity().MODE_PRIVATE).edit();
spe.putInt("tank", mTank);
spe.commit();
Log.d(TAG, "Saved data: tank = " + String.valueOf(mTank));
}
void loadData() {
SharedPreferences sp = getActivity().getPreferences(getActivity().MODE_PRIVATE);
mTank = sp.getInt("tank", 2);
Log.d(TAG, "Loaded data: tank = " + String.valueOf(mTank));
}
// We're being destroyed. It's important to dispose of the helper here!
@Override
public void onDestroy() {
super.onDestroy();
// very important:
Log.d(TAG, "Destroying helper.");
if (mHelper != null) {
mHelper.dispose();
mHelper = null;
}
}
// Listener that's 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(getString(R.string.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 && verifyDeveloperPayload(premiumPurchase));
Log.d(TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));
// Do we have the infinite gas plan?
Purchase infiniteGasPurchase = inventory
.getPurchase(SKU_INFINITE_GAS);
mSubscribedToInfiniteGas = (infiniteGasPurchase != null && verifyDeveloperPayload(infiniteGasPurchase));
Log.d(TAG, "User "
+ (mSubscribedToInfiniteGas ? "HAS" : "DOES NOT HAVE")
+ " infinite gas subscription.");
if (mSubscribedToInfiniteGas)
mTank = TANK_MAX;
// Check for gas delivery -- if we own gas, we should fill up the
// tank immediately
Purchase gasPurchase = inventory.getPurchase(SKU_GAS);
if (gasPurchase != null && verifyDeveloperPayload(gasPurchase)) {
Log.d(TAG, "We have gas. Consuming it.");
mHelper.consumeAsync(inventory.getPurchase(SKU_GAS),
mConsumeFinishedListener);
return;
}
updateUi();
setWaitScreen(false);
Log.d(TAG, "Initial inventory query finished; enabling main UI.");
}
};
/** Verifies the developer payload of a purchase. */
boolean verifyDeveloperPayload(Purchase p) {
String payload = p.getDeveloperPayload();
/*
* TODO: verify that the developer payload of the purchase is correct.
* It will be the same one that you sent when initiating the purchase.
*
* WARNING: Locally generating a random string when starting a purchase
* and verifying it here might seem like a good approach, but this will
* fail in the case where the user purchases an item on one device and
* then uses your app on a different device, because on the other device
* you will not have access to the random string you originally
* generated.
*
* So a good developer payload has these characteristics:
*
* 1. If two different users purchase an item, the payload is different
* between them, so that one user's purchase can't be replayed to
* another user.
*
* 2. The payload must be such that you can verify it even when the app
* wasn't the one who initiated the purchase flow (so that items
* purchased by the user on one device work on other devices owned by
* the user).
*
* Using your own server to store and verify developer payloads across
* app installations is recommended.
*/
return true;
}
// Callback for when a purchase is finished
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.d(TAG, "Purchase finished: " + result + ", purchase: "
+ purchase);
// if we were disposed of in the meantime, quit.
if (mHelper == null)
return;
if (result.isFailure()) {
complain(getString(R.string.error_purchase) + result);
setWaitScreen(false);
return;
}
if (!verifyDeveloperPayload(purchase)) {
complain(getString(R.string.error_purchase_authenitcity_failed));
setWaitScreen(false);
return;
}
Log.d(TAG, "Purchase successful.");
if (purchase.getSku().equals(SKU_GAS)) {
// bought 1/4 tank of gas. So consume it.
Log.d(TAG, "Purchase is gas. Starting gas consumption.");
mHelper.consumeAsync(purchase, mConsumeFinishedListener);
} else if (purchase.getSku().equals(SKU_PREMIUM)) {
// bought the premium upgrade!
Log.d(TAG, "Purchase is premium upgrade. Congratulating user.");
alert(getString(R.string.thank_you_updgraing_premium));
mIsPremium = true;
updateUi();
setWaitScreen(false);
} else if (purchase.getSku().equals(SKU_INFINITE_GAS)) {
// bought the infinite gas subscription
Log.d(TAG, "Infinite gas subscription purchased.");
alert("Thank you for subscribing to infinite gas!");
mSubscribedToInfiniteGas = true;
mTank = TANK_MAX;
updateUi();
setWaitScreen(false);
}
}
};
// Called when consumption is complete
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase, IabResult result) {
Log.d(TAG, "Consumption finished. Purchase: " + purchase
+ ", result: " + result);
// 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...
if (result.isSuccess()) {
// successfully consumed, so we apply the effects of the item in
// our
// game world's logic, which in our case means filling the gas
// tank a bit
Log.d(TAG, "Consumption successful. Provisioning.");
mTank = mTank == TANK_MAX ? TANK_MAX : mTank + 1;
saveData();
alert("You filled 1/4 tank. Your tank is now "
+ String.valueOf(mTank) + "/4 full!");
} else {
complain("Error while consuming: " + result);
}
updateUi();
setWaitScreen(false);
Log.d(TAG, "End consumption flow.");
}
};
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + ","
+ data);
if (mHelper == null)
return;
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
} else {
Log.d(TAG, "onActivityResult handled by IABUtil.");
}
}
//In app billing method end here
私の製品は開発者アカウントManaged products
のIn-app Products
です
編集:
Android.test.purchased
をSKU
として使用すると、正常に機能し、sku
をproduct_id
に変更すると、Error Authentication is required. You need to sign into your Google Account.
が得られます。
アプリ内購入用のプレイストアの商品IDが「test_product」であることを確認してください。コードでは同じSKUを使用する必要があります。また、アプリケーションからSKUを変更する場合は、考えられるすべてのSKU名がアプリ内製品としてPlayストアに存在する必要があります。私はかつてこの問題に遭遇しましたが、その理由は、SKUアイテムがGoogle Playstoreのアプリ内製品に存在しなかったためです。Playstoreに追加するだけで、解決されました。
APKを公開する必要があるようです。私もこの問題にこだわった。
少し長めですが、「GooglePlayストア」アプリが誤動作している可能性があります。
アプリ内購入ができないというお客様からの報告があり、同じエラーが発生しています。また、まだ購入が進んでいることも知っています。ですから、必ずしも何か間違った設定をしたとは思いません。 IAB v3は、単に「GooglePlayストア」アプリで誤動作している可能性があります。
[設定]に移動して、アプリケーションマネージャーを開き、リストから[Google Playストア]アプリケーションを選択して、そこに表示される[アップデートのアンインストール]ボタンをクリックしてみてください。次に、もう一度購入してみてください。古いGooglePlayアプリケーションがアプリ内課金v3をサポートしている場合は、機能します。そうでない場合は、「Google Playストア」アプリをもう一度更新してみてください。おそらく、ストアアプリの新しいバージョンまたは異なるバージョンを入手して、購入できるようになります。
別の方法として、別のGoogleアカウントで2つ目のテストデバイスを設定してみるか、友人にベータテストグループに参加してテスト購入を依頼することができます。
IABv3にはあまり満足していません。少し不格好です。 V2は機能が少ないですが、しっかりしていると感じました。