如何在Android应用程序中实现应用程序内结算?
在Android应用中实现应用内结算似乎相当复杂。 我怎么能这样做? SDK中的示例应用程序只有一个Activity,这对于像我的应用程序那样具有多个活动的应用程序来说过于简化。
那么,我会试着解释我所经历的。 我不认为自己是这方面的专家,但是我已经好几天了。
对于初学者来说,我非常难以理解示例和应用程序的工作stream程。 我认为最好从一个简单的例子开始,但是很难将代码分成小块,不知道是否破坏了任何东西。 我会告诉你我有什么,以及我从示例中改变了什么,以使其工作。
我有一个活动,我的所有购买来自。 这叫做Pro。
首先,您应该使用公开市场开发人员密钥更新Security类中的variablesbase64EncodedPublicKey,否则您将看到一个很好的exception。
那么,我将我的活动绑定到我的BillingService,如下所示:
public class Pro extends TrackedActivity implements OnItemClickListener { private BillingService mBillingService; private BillingPurchaseObserver mBillingPurchaseObserver; private Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.pro); //Do my stuff mBillingService = new BillingService(); mBillingService.setContext(getApplicationContext()); mHandler = new Handler(); mBillingPurchaseObserver = new BillingPurchaseObserver(mHandler); } } @Override protected void onStart() { //Register the observer to the service super.onStart(); ResponseHandler.register(mBillingPurchaseObserver); } @Override protected void onStop() { //Unregister the observer since you dont need anymore super.onStop(); ResponseHandler.unregister(mBillingPurchaseObserver); } @Override protected void onDestroy() { //Unbind the service super.onDestroy(); mBillingService.unbind(); }
这样,所有的购买交谈这个服务,然后,将JSON请求发送到市场。 你可能会认为采购是在同一时间进行的,但没有。 您发送请求,购买可能在几分钟或几小时后。 我认为这主要是服务器超载和信用卡的批准。
然后我有一个ListView与我的物品,我打开一个AlertDialog每一个,邀请他们购买物品。 当他们点击一个项目,我这样做:
private class BuyButton implements DialogInterface.OnClickListener { private BillingItem item = null; private String developerPayload; public BuyButton(BillingItem item, String developerPayload) { this.item = item; this.developerPayload = developerPayload; } @Override public void onClick(DialogInterface dialog, int which) { if (GeneralHelper.isOnline(getApplicationContext())){ //I track the buy here with GA SDK. mBillingService.requestPurchase(this.item.getSku(), this.developerPayload); } else { Toast.makeText(getApplicationContext(), R.string.msg_not_online, Toast.LENGTH_SHORT).show(); } } }
好吧,你应该看到市场打开,用户要么完成或取消购买。
那么重要的是我的PurChaseObserver,它处理所有市场发送的事件。 这是一个剥离的版本,但你应该明白(见我的意见通过代码):
private class BillingPurchaseObserver extends PurchaseObserver { public BillingPurchaseObserver(Handler handler) { super(Pro.this, handler); } @Override public void onBillingSupported(boolean supported) { if (supported) { //Enable buy functions. Not required, but you can do stuff here. The market first checks if billing is supported. Maybe your country is not supported, for example. } else { Toast.makeText(getApplicationContext(), R.string.billing_not_supported, Toast.LENGTH_LONG).show(); } } @Override public void onPurchaseStateChange(PurchaseState purchaseState, String itemId, int quantity, long purchaseTime, String developerPayload) { //This is the method that is called when the buy is completed or refunded I believe. // Here you can do something with the developerPayload. Its basically a Tag you can use to follow your transactions. i dont use it. BillingItem item = BillingItem.getBySku(getApplicationContext(), itemId); if (purchaseState == PurchaseState.PURCHASED) { if (item != null){ //This is my own implementation that sets the item purchased in my database. BillingHelper is a class with methods I use to check if the user bought an option and update the UI. You should also check for refunded. You can see the Consts class to find what you need to check for. boolean resu = item.makePurchased(getApplicationContext()); if (resu){ Toast.makeText(getApplicationContext(), R.string.billing_item_purchased, Toast.LENGTH_LONG).show(); } } } } private void trackPurchase(BillingItem item, long purchaseTime) { //My code to track the purchase in GA } @Override public void onRequestPurchaseResponse(RequestPurchase request, ResponseCode responseCode) { //This is the callback that happens when you sent the request. It doesnt mean you bought something. Just that the Market received it. if (responseCode == ResponseCode.RESULT_OK) { Toast.makeText(getApplicationContext(), R.string.billing_item_request_sent, Toast.LENGTH_SHORT).show(); } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) { //The user canceled the item. } else { //If it got here, the Market had an unexpected problem. } } @Override public void onRestoreTransactionsResponse(RestoreTransactions request, ResponseCode responseCode) { if (responseCode == ResponseCode.RESULT_OK) { //Restore transactions should only be run once in the lifecycle of your application unless you reinstalled the app or wipe the data. SharedPreferences.Editor edit = PreferencesHelper.getInstance().getDefaultSettings(getApplicationContext()).edit(); edit.putBoolean(Consts.DB_INITIALIZED, true); edit.commit(); } else { //Something went wrong } } }
我相信你不需要编辑别的东西。 其余的代码“工作”。 您可以尝试首先在自己的项目“android.test.purchased”中使用示例SKU。 到目前为止,我已经testing了这个,它的工作,但我仍然需要覆盖像退款状态的一切。 在这种情况下,我让用户保持function,但我想确保它在完成之前完美工作。
我希望它可以帮助你和其他人。
V3:这里是一个快速入门的教程..他使用从谷歌示例(Trivial Drive)的助手类…良好的第一个“你好计费”..
对于应用内结算v3,我发现这非常有帮助。
http://blog.blundellapps.com/simple-inapp-billing-payment-v3/
这里给出了一个Android In-App Billing v3的完整例子。 请使用ServiceConnection类来检查教程: Android In-App Billing v3
希望它会有所帮助。
有关更多说明,请参阅本教程: 在版本3 API中实施应用内结算
在我们的项目中集成应用内结算库的步骤
更新你的AndroidManifest.xml文件。
创build一个ServiceConnection并将其绑定到IInAppBillingService。
将应用内结算请求从应用程序发送到IInAppBillingService。
处理来自Google Play的应用内结算回复。
更新AndroidManifest.xml
<uses-permission android:name="com.android.vending.BILLING" />
在Manifest.xml文件中添加权限
将AIDL文件添加到您的项目
build立你的应用程序 您应该在项目的/ gen目录中看到名为IInAppBillingService.java的生成文件。
更新build.gradle文件中的依赖关系
apply plugin: 'com.android.application' android { compileSdkVersion 24 buildToolsVersion "24.0.0" defaultConfig { applicationId "com.inducesmile.androidinapppurchase" minSdkVersion 14 targetSdkVersion 24 versionCode 2 versionName "1.1" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:24.1.1' compile 'com.intuit.sdp:sdp-android:1.0.3' compile 'com.android.support:support-annotations:24.1.1' compile 'org.jetbrains:annotations-java5:15.0' }
InAppPurchaseActivity.java和activity_in_app_purchase.xml
这就是为我们的应用用户提供进行应用内购买的机会的地方。 在布局文件中,我们将给予用户以不同教派进行购买的机会。
InAppPurchaseActivity.java
注意:应该在非UI线程中调用getAllUserPurchase()和itemPurchaseAvailability()方法以避免应用程序崩溃。
public class InAppPurchaseActivity extends AppCompatActivity { private static final String TAG = InAppPurchaseActivity.class.getSimpleName(); private IInAppBillingService mService; private CustomSharedPreference customSharedPreference; String[] productIds = new String[]{Helper.ITEM_ONE_ID, Helper.ITEM_TWO_ID, Helper.ITEM_THREE_ID}; private ImageView buyOneButton, buyTwoButton, buyThreeButton; private static final char[] symbols = new char[36]; static { for (int idx = 0; idx < 10; ++idx) symbols[idx] = (char) ('0' + idx); for (int idx = 10; idx < 36; ++idx) symbols[idx] = (char) ('a' + idx - 10); } private String appPackageName; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_in_app_purchase); appPackageName = this.getPackageName(); Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); serviceIntent.setPackage("com.android.vending"); bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); customSharedPreference = new CustomSharedPreference(InAppPurchaseActivity.this); buyOneButton = (ImageView)findViewById(R.id.buy_one); buyOneButton.setVisibility(View.GONE); assert buyOneButton != null; buyOneButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(!isBillingSupported()){ Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support)); return; } purchaseItem(Helper.ITEM_ONE_ID); } }); buyTwoButton = (ImageView)findViewById(R.id.buy_two); buyTwoButton.setVisibility(View.GONE); assert buyTwoButton != null; buyTwoButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(!isBillingSupported()){ Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support)); return; } purchaseItem(Helper.ITEM_TWO_ID); } }); buyThreeButton = (ImageView)findViewById(R.id.buy_three); buyThreeButton.setVisibility(View.GONE); assert buyThreeButton != null; buyThreeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(!isBillingSupported()){ Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support)); return; } purchaseItem(Helper.ITEM_THREE_ID); } }); } ServiceConnection mServiceConn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { mService = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = IInAppBillingService.Stub.asInterface(service); AvailablePurchaseAsyncTask mAsyncTask = new AvailablePurchaseAsyncTask(appPackageName); mAsyncTask.execute(); } }; private void purchaseItem(String sku){ String generatedPayload = getPayLoad(); customSharedPreference.setDeveloperPayLoad(generatedPayload); try { Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), sku, "inapp", generatedPayload); PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT"); try { startIntentSenderForResult(pendingIntent.getIntentSender(), Helper.RESPONSE_CODE, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0)); } catch (IntentSender.SendIntentException e) { e.printStackTrace(); } } catch (RemoteException e) { e.printStackTrace(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == Helper.RESPONSE_CODE) { int responseCode = data.getIntExtra("RESPONSE_CODE", 0); String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE"); if (resultCode == RESULT_OK) { try { JSONObject purchaseJsonObject = new JSONObject(purchaseData); String sku = purchaseJsonObject.getString("productId"); String developerPayload = purchaseJsonObject.getString("developerPayload"); String purchaseToken = purchaseJsonObject.getString("purchaseToken"); //the developerPayload value is better stored in remote database but in this tutorial //we will use a shared preference for(int i = 0; i < productIds.length; i++){ if(productIds[i].equals(sku) && developerPayload.equals(customSharedPreference.getDeveloperPayload())){ customSharedPreference.setPurchaseToken(purchaseToken); //access to private content Intent contentIntent = new Intent(InAppPurchaseActivity.this, PrivateContentActivity.class); startActivity(contentIntent); } } } catch (JSONException e) { e.printStackTrace(); } } } } private String getPayLoad(){ RandomString randomString = new RandomString(36); String payload = randomString.nextString(); return payload; } public class RandomString { private final Random random = new Random(); private final char[] buf; public RandomString(int length) { if (length < 1) throw new IllegalArgumentException("length < 1: " + length); buf = new char[length]; } public String nextString() { for (int idx = 0; idx < buf.length; ++idx) buf[idx] = symbols[random.nextInt(symbols.length)]; return new String(buf); } } public final class SessionIdentifierGenerator { private SecureRandom random = new SecureRandom(); public String nextSessionId() { return new BigInteger(130, random).toString(32); } } private class AvailablePurchaseAsyncTask extends AsyncTask<Void, Void, Bundle> { String packageName; public AvailablePurchaseAsyncTask(String packageName){ this.packageName = packageName; } @Override protected Bundle doInBackground(Void... voids) { ArrayList<String> skuList = new ArrayList<String>(); skuList.add(Helper.ITEM_ONE_ID); skuList.add(Helper.ITEM_TWO_ID); skuList.add(Helper.ITEM_THREE_ID); Bundle query = new Bundle(); query.putStringArrayList(Helper.ITEM_ID_LIST, skuList); Bundle skuDetails = null; try { skuDetails = mService.getSkuDetails(3, packageName, "inapp", query); } catch (RemoteException e) { e.printStackTrace(); } return skuDetails; } @Override protected void onPostExecute(Bundle skuDetails) { List<AvailablePurchase> canPurchase = new ArrayList<AvailablePurchase>(); int response = skuDetails.getInt("RESPONSE_CODE"); if (response == 0) { ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST"); if(responseList != null){ for (String thisResponse : responseList) { JSONObject object = null; try { object = new JSONObject(thisResponse); String sku = object.getString("productId"); String price = object.getString("price"); canPurchase.add(new AvailablePurchase(sku, price)); } catch (JSONException e) { e.printStackTrace(); } } } } if(checkIfPurchaseIsAvailable(canPurchase, productIds[0])){ buyOneButton.setVisibility(View.VISIBLE); }else{ buyOneButton.setVisibility(View.GONE); } if(checkIfPurchaseIsAvailable(canPurchase, productIds[1])){ buyTwoButton.setVisibility(View.VISIBLE); }else{ buyTwoButton.setVisibility(View.GONE); } if(checkIfPurchaseIsAvailable(canPurchase, productIds[2])){ buyThreeButton.setVisibility(View.VISIBLE); }else{ buyThreeButton.setVisibility(View.GONE); } } } @org.jetbrains.annotations.Contract("null, _ -> false") private boolean checkIfPurchaseIsAvailable(List<AvailablePurchase> all, String productId){ if(all == null){ return false;} for(int i = 0; i < all.size(); i++){ if(all.get(i).getSku().equals(productId)){ return true; } } return false; } public boolean isBillingSupported(){ int response = 1; try { response = mService.isBillingSupported(3, getPackageName(), "inapp"); } catch (RemoteException e) { e.printStackTrace(); } if(response > 0){ return false; } return true; } public void consumePurchaseItem(String purchaseToken){ try { int response = mService.consumePurchase(3, getPackageName(), purchaseToken); if(response != 0){ return; } } catch (RemoteException e) { e.printStackTrace(); } } public Bundle getAllUserPurchase(){ Bundle ownedItems = null; try { ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null); } catch (RemoteException e) { e.printStackTrace(); } return ownedItems; } public List<UserPurchaseItems> extractAllUserPurchase(Bundle ownedItems){ List<UserPurchaseItems> mUserItems = new ArrayList<UserPurchaseItems>(); int response = ownedItems.getInt("RESPONSE_CODE"); if (response == 0) { ArrayList<String> ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST"); ArrayList<String> purchaseDataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); ArrayList<String> signatureList = ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST"); String continuationToken = ownedItems.getString("INAPP_CONTINUATION_TOKEN"); if(purchaseDataList != null){ for (int i = 0; i < purchaseDataList.size(); ++i) { String purchaseData = purchaseDataList.get(i); assert signatureList != null; String signature = signatureList.get(i); assert ownedSkus != null; String sku = ownedSkus.get(i); UserPurchaseItems allItems = new UserPurchaseItems(sku, purchaseData, signature); mUserItems.add(allItems); } } } return mUserItems; } @Override public void onDestroy() { super.onDestroy(); if (mService != null) { unbindService(mServiceConn); } } }
创build帮助程序包目录
创build一个新的包文件夹并将其命名助手。 在包里面,创build一个新的java文件Helper.java。
Helper.java
public class Helper { public static final String ITEM_ID_LIST = "ITEM_ID_LIST"; public static final String ITEM_ONE_ID = "productone"; public static final String ITEM_TWO_ID = "producttwo"; public static final String ITEM_THREE_ID = "productthree"; public static final int RESPONSE_CODE = 1001; public static final String SHARED_PREF = "shared_pref"; public static final String DEVELOPER_PAYLOAD = "developer_payload"; public static final String PURCHASE_TOKEN = "purchase_token"; public static void displayMessage(Context context, String message){ Toast.makeText(context.getApplicationContext(), message, Toast.LENGTH_LONG).show(); } }
testing应用内结算购买
- 创build一个Google+帐户(不要使用主帐户)
- 添加将在您的组或社区中testing应用程序的用户。
错误您可能会在应用内购买testing期间遇到错误
您所要求的物品不可购买
解决scheme – 根据Stackoverflow的AndreiBogdan ,
所有功劳归功于Inducesmile他的教程
Android开发人员博客还推荐一个关于销售应用内商品的培训课程。 要查看完整的实现并学习如何testing应用程序,请查看本教程: 销售应用内商品
如果您想使用简单的库在Google Play和Amazon Appstore上发布,则可以使用RoboBillingLibrary 。 它将两者的细节都抽象成一个易于使用的库。 详细说明在Github页面上。