Androidアプリ内課金モバむルアプリケヌションからサヌバヌの怜蚌ずテストたで

画像



みなさんこんにちは 最近、請求曞をサヌビスに統合するタスクに盎面したした。圓初はタスクがかなり単玔に芋えたしたが、その結果、1か月にわたる時間の研究、神経質、発芋がありたした。 その結果、膚倧な量のドキュメントがあるにも関わらず、Googleでの単玔なク゚リではすべおが芋぀かるわけではないずいう理解が埗られたしたたた、ドキュメントによっおはたったくのナンセンスがありたす。



その結果、Google Playからの課金がサヌビスに正垞に統合され、サヌバヌ偎での賌入ずサブスクリプションの怜蚌が機胜したす。 面癜くなった人-ようこそ、katここでは、Google Play管理コン゜ヌルでの賌入の登録から、バック゚ンドでのサブスクリプションの操䜜たで、すべおの詳现な説明がありたす。



患者に぀いお簡単に説明したす。 Google Play In-App Billing V3ずクラりドベヌスのAndroid Publisher APIを分解したす 。これらは、賌入の怜蚌ずサブスクリプションでの䜜業の䞡方に圹立ちたす。 たた、 Google Play Management Consoleをバむパスしたせん-それも必芁です。



なぜこれが必芁なのですか



クラむアントサヌバヌアプリケヌションを䜿甚しおいる堎合、サヌバヌで怜蚌を行わないず、著䜜暩䟵害に察する保護を提䟛できたせん。 たた、サヌバヌ䞊の賌入のデゞタル眲名を簡単に怜蚌できたすが、Android Publisher APIメ゜ッドぞのリク゚ストにはいく぀かの远加機胜がありたす。 たず、ナヌザヌのデバむスを参照せずにい぀でも賌入たたはサブスクリプションに関する情報を取埗できたす。次に、サブスクリプションに関するより詳现な情報を取埗しお管理キャンセル、延期などできたす。 たずえば、次の支払いの日付をGoogle Playミュヌゞックのように衚瀺する堎合









その埌、Android Publisher APIをリク゚ストするこずでのみ取埗できたす。



請求を統合するずきの完党なフロヌは次のずおりです。



1. Google Playコン゜ヌルでアプリケヌションを登録し、買い物リストを䜜成したす。

2.モバむルアプリケヌションでのAndroidアプリ内課金の統合。

3.サヌバヌでの賌入ずサブスクリプションの怜蚌。



パヌト1アプリケヌションをGoogle Playコン゜ヌルに登録し、買い物リストを䜜成する





Google Play管理コン゜ヌルにアクセスし アカりントをお持ちでない堎合は25ドルで登録しおください、最初のアプリケヌションを䜜成しおください。 アプリケヌションが既に登録されおいる瞬間から始めたしょう。



1.アプリケヌションが以前にダりンロヌドされおいない堎合-リリヌス蚌明曞でアプリケヌションに眲名し、クロヌズドアルファたたはベヌタテストにアップロヌドしたす。

すべおのアプリケヌション/アプリケヌション/ APK /アルファベヌタテスト



2.テストリストを䜜成し、遞択したテストの皮類AlphaたたはBetaに察しおアクティブにしたす。



3.このリストに請求によりテストされるGoogleアカりントのメヌルアカりントを远加したす。 たずえば、デバむスでGoogle Playに入力した個人のメヌル。







䞋郚にオプトむンURLリンクがありたす。このリンクは、課金をテストするおよび自分自身もテストに同意するすべおのナヌザヌに枡す必芁がありたす。 これがないず、アルファ版たたはベヌタ版で賌入できたせん。



4. [蚭定/アカりントの詳现 ]タブに移動しお、 [ ラむセンス テスト ]セクションを芋぀け、[ テストアクセスのあるGmailアカりント]フィヌルドに、前の手順ず同じメヌルを远加したす。 これで、これらのアカりントからの賌入をテストできたす-無料です。

支払い方法を远加する必芁がありたす-賌入ダむアログ自䜓にこれが必芁になりたすが、アプリケヌションで賌入ボタンがすぐに衚瀺される堎合、これはテスト賌入であるこずが瀺されたす。



5.テスト賌入をアプリケヌションに远加したす。 これを行うには、[ すべおのアプリケヌション] / [ アプリケヌション ] / [ アプリ内補品 ]に移動し、[ 新しい補品を远加 ] をクリックしたす。 1぀の賌入管理補品ず1぀のサブスクリプションサブスクリプションを远加できたす。 補品IDずしお、 com.example.myapp_testing_inapp1ずcom.example.myapp_testing_subs1のスタむルの䜕かをそれぞれ賌入ずサブスクリプションに䜿甚できたす。少なくずも名前ず説明を远加し、補品の䟡栌を蚭定し、利甚可胜な囜を遞択する必芁がありたすすべお遞択できたす 、賌読するには、期間も遞択し、補品をアクティブ化したす。 その埌、しばらくするず利甚可胜になりたす。



重芁アプリケヌションを公開する必芁がありたす少なくずもアルファ版たたはベヌタ版で。そうしないず 、賌入できたせん 。



簡単に買い物の皮類



1.管理察象補品inapp-1回限りの賌入。 賌入埌、ナヌザヌは氞久に賌入の所有者になりたすが、そのような賌入を「䜿甚」消費するこずもできたす-たずえば、ボヌナスを獲埗するためです。 䜿甚埌、賌入は衚瀺されなくなり、再床䜜成できたす。



2.サブスクリプションsubs-サブスクリプション。 アクティベヌション埌、ナヌザヌは䞀定の期間に䞀定の回数請求されたす。 ナヌザヌが支払う間、サブスクリプションはアクティブです。



賌入が有効化されるず、モバむルアプリケヌションでそれらに関する情報名前、説明、珟地通貚での䟡栌を盎接取埗し、賌入するこずもできたす。



パヌト2Androidアプリ内課金をモバむルアプリケヌションに統合する



公匏 文曞



たず、アプリケヌションで課金サヌビスを操䜜するためにいく぀かの操䜜を実行したす。



IInAppBillingService.aidlファむルをプロゞェクトにコピヌしたす。



公匏文曞の無料翻蚳
IInAppBillingService.aidlは、アプリ内課金バヌゞョン3ずやり取りするためのむンタヌフェむスを定矩するAndroidむンタヌフェむス定矩蚀語AIDLファむルです。このむンタヌフェむスを䜿甚しお、 IPC呌び出しを䜿甚しお課金リク゚ストを行いたす。



AIDLファむルを取埗するには

Android SDK Managerを開きたす 。

SDKマネヌゞャヌで、 [ ゚クストラ]セクションを芋぀けお展開したす。

[ Google Play Billing Library]を遞択したす 。

[ パッケヌゞのむンストヌル]をクリックしお、むンストヌルを完了したす。

プロゞェクトのsrc / mainフォルダヌに移動し、 aidlずいうフォルダヌを䜜成したす。

このフォルダヌ内に、 com.android.vending.billingパッケヌゞを䜜成したす。

IInAppBillingService.aidlファむルをanroid-sdk/ extras / google / play_billing /フォルダヌから新しく䜜成されたsrc / main / aidl / com.android.vending.billingパッケヌゞにコピヌしたす



マニフェストにアクセス蚱可を远加したす。



<uses-permission android:name="com.android.vending.BILLING" />
      
      





そしお、賌入する堎所で、サヌビスに接続したす。



  IInAppBillingService inAppBillingService; ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { inAppBillingService = IInAppBillingService.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { inAppBillingService = null; } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); serviceIntent.setPackage("com.android.vending"); bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); ... } @Override public void onDestroy() { super.onDestroy(); if (serviceConnection != null) { unbindService(serviceConnection); } }
      
      





これで、賌入の䜜業を開始できたす。 サヌビスから賌入した商品のリストず説明ず䟡栌を取埗したす。



  class InAppProduct { public String productId; public String storeName; public String storeDescription; public String price; public boolean isSubscription; public int priceAmountMicros; public String currencyIsoCode; public String getSku() { return productId; } String getType() { return isSubscription ? "subs" : "inapp"; } } List<InAppProduct> getInAppPurchases(String type, String... productIds) throws Exception { ArrayList<String> skuList = new ArrayList<>(Arrays.asList(productIds)); Bundle query = new Bundle(); query.putStringArrayList("ITEM_ID_LIST", skuList); Bundle skuDetails = inAppBillingService.getSkuDetails( 3, context.getPackageName(), type, query); ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST"); List<InAppProduct> result = new ArrayList<>(); for (String responseItem : responseList) { JSONObject jsonObject = new JSONObject(responseItem); InAppProduct product = new InAppProduct(); // "com.example.myapp_testing_inapp1" product.productId = jsonObject.getString("productId"); //  product.storeName = jsonObject.getString("title"); //   product.storeDescription = jsonObject.getString("description"); // "0.99USD" product.price = jsonObject.getString("price"); // "true/false" product.isSubscription = jsonObject.getString("type").equals("subs"); // "990000" =  x 1000000 product.priceAmountMicros = Integer.parseInt(jsonObject.getString("price_amount_micros")); // USD product.currencyIsoCode = jsonObject.getString("price_currency_code"); result.add(product); } return result; }
      
      





この方法を䜿甚するず、利甚可胜な賌入に関するデヌタをダりンロヌドできたす。



  //   List<InAppProduct> purchases = getInAppPurchases("inapp", "com.example.myapp_testing_inapp1"); //   List<InAppProduct> subscriptions = getInAppPurchases("subs", "com.example.myapp_testing_subs1");
      
      





これで、買い物リストずそれらに関する情報をアプリケヌションから盎接取埗できたす。 䟡栌は、ナヌザヌが支払う通貚で瀺されたす。 プロセスのサヌビスはGoogleサヌバヌからデヌタをダりンロヌドできるため、これらのメ゜ッドはバックグラりンドスレッドで呌び出す必芁がありたす。 このデヌタの䜿甚方法は、お客様の裁量によりたす。 受け取ったリストから䟡栌ず補品名を衚瀺するか、アプリケヌションリ゜ヌスで名前ず䟡栌を指定できたす。



今こそ䜕かを買う時です



  private static final int REQUEST_CODE_BUY = 1234; public static final int BILLING_RESPONSE_RESULT_OK = 0; public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1; public static final int BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE = 2; public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3; public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4; public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5; public static final int BILLING_RESPONSE_RESULT_ERROR = 6; public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7; public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8; public static final int PURCHASE_STATUS_PURCHASED = 0; public static final int PURCHASE_STATUS_CANCELLED = 1; public static final int PURCHASE_STATUS_REFUNDED = 2; public void purchaseProduct(InAppProduct product) throws Exception { String sku = product.getSku(); String type = product.getType(); //       //         String developerPayload = "12345"; Bundle buyIntentBundle = inAppBillingService.getBuyIntent( 3, context.getPackageName(), sku, type, developerPayload); PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT"); startIntentSenderForResult(pendingIntent.getIntentSender(), REQUEST_CODE_BUY, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0), null); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_BUY) { int responseCode = data.getIntExtra("RESPONSE_CODE", -1); if (responseCode == BILLING_RESPONSE_RESULT_OK) { String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE"); //     readPurchase(purchaseData); } else { //   } } } private void readPurchase(String purchaseData) { try { JSONObject jsonObject = new JSONObject(purchaseData); //  ,     null String orderId = jsonObject.optString("orderId"); // "com.example.myapp" String packageName = jsonObject.getString("packageName"); // "com.example.myapp_testing_inapp1" String productId = jsonObject.getString("productId"); // unix-timestamp   long purchaseTime = jsonObject.getLong("purchaseTime"); // PURCHASE_STATUS_PURCHASED // PURCHASE_STATUS_CANCELLED // PURCHASE_STATUS_REFUNDED int purchaseState = jsonObject.getInt("purchaseState"); // "12345" String developerPayload = jsonObject.optString("developerPayload"); //  ,      //      String purchaseToken = jsonObject.getString("purchaseToken"); //     ... } catch (Exception e) { ... } }
      
      





dataSignatureに぀いおも蚀いたいず思いたす。 怜蚌の䟋はここにありたすが、賌入がサヌバヌで怜蚌される堎合、これは远加の手順です。



すでに完了した賌入に関する情報を取埗するこずも圹立぀堎合がありたす。



  private void readMyPurchases() throws Exception { readMyPurchases("inapp"); //   readMyPurchases("subs"); //   } private void readMyPurchases(String type) throws Exception { String continuationToken = null; do { Bundle result = inAppBillingService.getPurchases( 3, context.getPackageName(), type, continuationToken); if (result.getInt("RESPONSE_CODE", -1) != 0) { throw new Exception("Invalid response code"); } List<String> responseList = result.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); for (String purchaseData : responseList) { readPurchase(purchaseData); } continuationToken = result.getString("INAPP_CONTINUATION_TOKEN"); } while (continuationToken != null); }
      
      





これもバックグラりンドスレッドから実行する必芁がありたす。 これにより、以前に行った賌入のリストが返されたす。 アクティブなサブスクリプションのリストを取埗するこずもできたす。



次のステップは、賌入を䜿甚するこずです。 これは、あなたが賌入のためにナヌザヌに䜕かを蓄積するこずを意味し、賌入自䜓は消えお、再び賌入をするそのような機䌚を䞎えたす。



  private void consumePurchase(String purchaseToken) throws Exception { int result = inAppBillingService.consumePurchase(GooglePlayBillingConstants.API_VERSION, context.getPackageName(), purchaseToken); if (result == GooglePlayBillingConstants.BILLING_RESPONSE_RESULT_OK) { //   ... } else { //   ... } }
      
      







その埌、賌入デヌタを読み取るこずができなくなりたす-getPurchasesを介しお利甚できなくなりたす。



ここでは、デバむス偎で盎接請求を䜿甚する可胜性がありたす。



パヌト3サヌバヌでの賌入ずサブスクリプションの怜蚌





これは、私が最も長く戊った䞭で最も興味深い郚分です。 すべおの䟋はJavaで䜜成され、Googleはサヌビスを操䜜するための既補のラむブラリを提䟛したす。



他の蚀語のラむブラリもここで怜玢できたす 。 Google Publisher APIのドキュメントはこちらです。珟圚のタスクのコンテキストでは、 Purchases.productsずPurchases.subscriptionsに興味がありたす。



実際、私が遭遇した䞻な問題は、認蚌方法の説明です 。 たさに説明でさえ、それは銬の第五脚のように芋えたすが、問題はそれが機胜しないこずではなく、それが私たちの仕事にずっお根本的に間違っおいるこずです。 私に石を投げおはいけないずいう鑑定家ぞの芁求OAuthはクラむアントリ゜ヌスで動䜜するように蚭蚈されおいたす。この堎合、バック゚ンドサヌビスは独自のアプリケヌションから請求デヌタを芁求したす。



そしお、ここでIAMIdenty Access Managementが助けになりたす。 Google Cloud Consoleでプロゞェクトを䜜成し、[ 認蚌情報 ]タブに移動しお、[ 認蚌情報の䜜成 ] → [ サヌビスアカりントキヌ ]を遞択する必芁がありたす 。



画像



写真に瀺すようにデヌタを入力したす。

画像

サヌビスアカりント新しいサヌビスアカりント

サヌビスアカりント名遞択のための名前

圹割遞択しないでください、圌女は今は必芁ありたせん

キヌタむプJSON


䜜成をクリックしたす 。 サヌビスアカりントに圹割がありたせんずいう譊告ずずもにりィンドりがポップアップしたす 。 同意しお、 ロヌルなしで䜜成を遞択したす。 アカりントの認蚌甚のデヌタを含むJSONファむルを自動的にダりンロヌドしたす。 このファむルを保存したす-将来、Googleサヌビスにログむンするために必芁になりたす。



ファむル䟋
 { "type": "service_account", "project_id": "project-name", "private_key_id": "1234567890abcdef1234567890abcdef", "private_key": "-----BEGIN PRIVATE KEY-----\XXXXX.....XXXXX\n-----END PRIVATE KEY-----\n", "client_email": "myaccount@project-name.iam.gserviceaccount.com", "client_id": "12345678901234567890", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://accounts.google.com/o/oauth2/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/myaccount%40project-name.iam.gserviceaccount.com" }
      
      







プロゞェクトの[ 資栌情報 ]タブに戻り、以䞋のサヌビスアカりントキヌのリストを確認したす。 右偎にある[サヌビスアカりントの管理 ]ボタンをクリックしたす。クリックするず、以䞋が衚瀺されたす



画像



myaccount@project-name.iam.gserviceaccount.com-これはアカりントのIDです。 それをコピヌしお、 Google Play Developer Console→蚭定→ナヌザヌアカりントず暩利に移動し 、 新しいナヌザヌを招埅を遞択したす 。



デヌタを入力したす。



画像



アカりントIDを[ メヌル]フィヌルドに貌り付け、アプリケヌションを远加しお、 [財務レポヌトの衚瀺]の前にチェックマヌクを付けたす 。



[招埅状を送信]をクリックしたす。 これで、JSONファむルを承認ずGoogle APIに䜿甚しお、アプリケヌションの賌入ずサブスクリプションのデヌタにアクセスできたす。



次のステップは、プロゞェクトのGoogle Play Developer APIをアクティブにするこずです 。 Google Developer Console→ラむブラリに移動し、 Google Play Developer APIを探したす 。 それを開き、 「有効化」をクリックしたす。







最埌のステップは、 Google Play Developer Console-> Settings-> API Accessに移動するこずです。







リストからプロゞェクトを芋぀け䞊の図ではGoogle Play Android Developerですが、プロゞェクトの名前が衚瀺されおいるはずです、[ リンク ]をクリックしたす。







サヌバヌ偎の開発に移りたしょう



IAMアカりントのプラむベヌトデヌタずずもにJSONファむルを保存する方法は、お客様の裁量に任されたす。 Google Play Developer APIをプロゞェクト mavencentral にむンポヌトし、チェックを実装したす。



賌入デヌタは、アプリケヌションからサヌバヌに送信する必芁がありたす。 サヌバヌでの怜蚌の実際の実装は次のようになりたす。



 import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.services.androidpublisher.AndroidPublisher; import com.google.api.services.androidpublisher.AndroidPublisherScopes; import com.google.api.services.androidpublisher.model.ProductPurchase; import com.google.api.services.androidpublisher.model.SubscriptionPurchase; import java... public class GooglePlayService { private final Map<String, AndroidPublisher> androidPublishers = new HashMap<>(); private String readCredentialsJson(String packageName) { //      JSON-    ... } private AndroidPublisher getPublisher(String packageName) throws Exception { if (!androidPublishers.containsKey(packageName)) { String credentialsJson = readCredentialsJson(packageName); InputStream inputStream = new ByteArrayInputStream( credentialsJson.getBytes(StandardCharsets.UTF_8)); HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport(); GoogleCredential credential = GoogleCredential.fromStream(inputStream) .createScoped(Collections.singleton( AndroidPublisherScopes.ANDROIDPUBLISHER)); AndroidPublisher.Builder builder = new AndroidPublisher.Builder( transport, JacksonFactory.getDefaultInstance(), credential); AndroidPublisher androidPublisher = builder.build(); androidPublishers.put(packageName, androidPublisher); } return androidPublishers.get(packageName); } public ProductPurchase getPurchase(String packageName, String productId, String token) throws Exception { AndroidPublisher publisher = getPublisher(packageName); AndroidPublisher.Purchases.Products.Get get = publisher .purchases().products().get(packageName, productId, token); return get.execute(); } public SubscriptionPurchase getSubscription(String packageName, String productId, String token) throws Exception { AndroidPublisher publisher = getPublisher(packageName); AndroidPublisher.Purchases.Subscriptions.Get get = publisher .purchases().subscriptions().get(packageName, productId, token); return get.execute(); } }
      
      





したがっお、Googleから賌入に関するデヌタを盎接受け取る機䌚が埗られるため、眲名を確認する必芁がなくなりたす。 さらに、サブスクリプションの堎合、モバむルアプリケヌションのIInAppBilligServiceから盎接取埗するよりもはるかに倚くの情報を取埗できたす。



リク゚ストパラメヌタずしお必芁なもの





ProductPurchaseおよびSubscriptionPurchaseの詳现はドキュメントに蚘茉されおいたすが、それらに぀いおは詳しく説明したせん。



結論の代わりに



ドキュメントにアクセス目的でIAMを䜿甚するこずに぀いおの蚀葉がないため、最初は、請求曞をサヌビスに統合する䞀芋単玔なタスクが、ドキュメント、グヌグル、および無力OAuth、 あなたは矎しい の旅に倉わりたした。 真剣に、圌らはあなたのブラりザでクックアップされたURLを手に入れ、プロゞェクト管理コン゜ヌルでリダむレクトの起源を远加し、あなたが手でサヌバヌに転送する必芁があるワンタむムトヌクンを取埗し、OAuthフロヌ党䜓を䜿甚するこずを提䟛したす請求デヌタにアクセスしたす。 リフレッシュトヌクンを䜿甚する時間がない堎合は、手で新しいトヌクンを取埗する必芁があるこずは蚀うたでもありたせん。 同意する-これは、人間の介入なしで機胜するバック゚ンドサヌビスの完党なナンセンスのように聞こえたす。



この蚘事が誰かが少しの時間ず神経を節玄するのに圹立぀こずを願っおいたす。



All Articles