این راهنما نحوه ادغام برنامه با «میاناهای برنامهسازی کاربردی کتابخانه خدمات صورتحساب Play» را شرح میدهد تا بتوانید به کاربران خود انتخاب صورتحساب ارائه دهید.
یکپارچهسازی با PBL
میتوانید انتخاب صورتحساب را در چهار سناریو با PBL ادغام کنید. سناریوها براساس اینکه چه کسی صفحه انتخاب را ارائه میدهد و پرداخت کجا انجام میشود متفاوت است. جدول زیر سناریوهای ادغام را شرح میدهد:
| کدام صفحه انتخاب صورتحساب را میخواهید پرداز کنید؟ | |||
| Google Play | خودتان (مطابق با دستورالعملهای تجربه کاربری) | ||
| پرداخت کجا انجام میشود؟ | درونبرنامه | سناریو 1A
Google صفحه انتخاب را پردازش میکند و صورتحساب جایگزین در برنامه شما مدیریت میشود. |
سناریو 1B
توسعهدهنده برنامه صفحه انتخاب را پردازش میکند و صورتحساب جایگزین در برنامه شما مدیریت میشود. |
| پیوند وب خارجی | سناریو 2A
Google صفحه انتخاب را پرداز میکند و کاربر برای خرید به وبسایتهای خودتان در خارج از برنامه پیوند داده میشود. |
سناریو ۲-ب
توسعهدهنده برنامه صفحه انتخاب را پرداز میکند و کاربر برای خرید به وبسایتهای خودتان در خارج از برنامه پیوند داده میشود. |
|
تصویر زیر جریان انتخاب صورتحساب را برای هریک از این سناریوها توضیح میدهد:
سناریوهای یکپارچهسازی PBL
بسته به سناریو یکپارچهسازی، مراحل این بخش را برای پیادهسازی انتخاب صورتحساب در برنامهتان دنبال کنید.
مدیریت سناریو 1A
Google صفحه انتخاب را پردازش میکند و صورتحساب جایگزین در برنامه شما مدیریت میشود برای فعال کردن انتخاب صورتحساب در این سناریو، مراحل زیر را انجام دهید:
هنگام ساختن نمونه BillingClient، با
EnableBillingProgramParamsباenableBillingProgramتماس بگیرید، و سپس اتصال را شروع کنید. برای مثال:کاتلین
// Build the parameters to enable the Billing Choice program and assign the listener // to handle user selection of the developer-provided billing option. val enableBillingProgramParams = EnableBillingProgramParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setDeveloperProvidedBillingListener(developerProvidedBillingListener) .build() // Build the parameters to enable support for pending purchases. val pendingPurchasesParams = PendingPurchasesParams.newBuilder() .enableOneTimeProducts() .build() // Construct the BillingClient instance with the purchases updated listener, // pending purchases support, and the billing choice params. val billingClient = BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .enablePendingPurchases(pendingPurchasesParams) .enableBillingProgram(enableBillingProgramParams) .build() // Establish a connection to Google Play val billingResult = suspendCancellableCoroutine<BillingResult> { continuation -> billingClient.startConnection(object : BillingClientStateListener { // Called when the connection setup process completes. override fun onBillingSetupFinished(billingResult: BillingResult) { // Resume the coroutine and pass back the BillingResult to the caller. if (continuation.isActive) { continuation.resume(billingResult) } } // Called if the connection to the Play Store service is dropped. // This prevents the await or suspension point from hanging indefinitely. override fun onBillingServiceDisconnected() { if (continuation.isActive) { continuation.resume( BillingResult.newBuilder() .setResponseCode(BillingResponseCode.SERVICE_DISCONNECTED) .setDebugMessage("Billing service disconnected during connection setup") .build() ) } } }) }
جاوا
EnableBillingProgramParams params = EnableBillingProgramParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setDeveloperProvidedBillingListener(developerProvidedBillingListener) .build(); BillingClient billingClient = BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .enablePendingPurchases( PendingPurchasesParams.newBuilder() .enableOneTimeProducts() .build() ) .enableBillingProgram(params) .build();تأیید کنید که انتخاب صورتحساب پردازششده توسط Google برای کاربر دردسترس است.
برای بررسی دردسترس بودن برنامه، با
isBillingProgramAvailableAsyncتماس بگیرید، و سپس برای نمایش محصولات دردسترس، باqueryProductDetailsAsyncتماس بگیرید. برای مثال:کاتلین
val (billingResult, billingProgramAvailabilityDetails) = billingClient.isBillingProgramAvailable(BillingProgram.BILLING_CHOICE) if (billingResult.responseCode == BillingResponseCode.OK) { val billingChoiceAvailabilityDetails = billingProgramAvailabilityDetails.billingChoiceAvailabilityDetails if (billingChoiceAvailabilityDetails != null && billingChoiceAvailabilityDetails.choiceScreenType == ChoiceScreenType.GOOGLE_RENDERED ) { // Billing choice is available. Query products and proceed. } else { // Fallback to other available programs. } } else { // Fallback to other available programs. }
جاوا
// ... billingClient.isBillingProgramAvailableAsync( BillingProgram.BILLING_CHOICE, (billingResult, billingProgramAvailabilityDetails) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK) { BillingChoiceAvailabilityDetails billingChoiceAvailabilityDetails = billingProgramAvailabilityDetails.getBillingChoiceAvailabilityDetails(); if (billingChoiceAvailabilityDetails != null && billingChoiceAvailabilityDetails.getChoiceScreenType() == ChoiceScreenType.GOOGLE_RENDERED) { // Billing choice is available. Query products and proceed. } else { // Fallback to other available programs. } } else { // Fallback to other available programs. } } );توجه:
billingProgramAvailabilityDetailsبه شما میگوید که صفحه انتخاب صورتحساب پردازششده توسط Google یا صفحه انتخاب صورتحساب پردازششده توسط «توسعهدهنده» دردسترس است یا نه.برای راهاندازی جریان خرید وقتی کاربر روی «خرید» کلیک میکند،
launchBillingFlowرا فراخوانی کنید. اگر انتخاب صورتحساب دردسترس است،DeveloperBillingOptionParamsرا بهBillingFlowParamsارسال کنید. برای مثال:کاتلین
val developerBillingOptionParams = DeveloperBillingOptionParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .build() val billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .enableDeveloperBillingOption(developerBillingOptionParams) .build() val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)
جاوا
DeveloperBillingOptionParams developerBillingOptionParams = DeveloperBillingOptionParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .build(); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .enableDeveloperBillingOption(developerBillingOptionParams) .build(); BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);توجه: کنترل والدین برای کاربران تحت نظارت نمایش داده میشود.
انتخاب نوع صورتحساب توسط کاربر را به این صورت مدیریت کنید:
- اگر کاربر «خدمات صورتحساب Play» را انتخاب کند، نتیجه صورتحساب به
PurchasesUpdatedListenerثبتشده در مرحله ۱ برگردانده میشود. - اگر کاربر صورتحساب جایگزین شما را انتخاب کند، نتیجه صورتحساب به
DeveloperProvidedBillingListenerثبتشده در مرحله ۱ برگردانده میشود.DeveloperProvidedBillingDetailsبرگشتی در این مورد شاملexternalTransactionTokenاست. از این کد برای گزارش تراکنش استفاده خواهد شد.
- اگر کاربر «خدمات صورتحساب Play» را انتخاب کند، نتیجه صورتحساب به
مدیریت سناریو 1B
توسعهدهنده صفحه انتخاب را ارائه میکند و صورتحساب جایگزین در درون برنامه شما مدیریت میشود. برای فعال کردن انتخاب صورتحساب در این سناریو، مراحل زیر را انجام دهید:
بدون
DeveloperProvidedBillingListenerدرEnableBillingProgramParams،enableBillingProgramرا هنگام ساختن نمونهBillingClientفراخوانی کنید، و سپس اتصال را شروع کنید. برای مثال:کاتلین
// Build the parameters to enable the Billing Choice program. val enableBillingProgramParams = EnableBillingProgramParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .build() // Build the parameters to enable support for pending purchases. val pendingPurchasesParams = PendingPurchasesParams.newBuilder() .enableOneTimeProducts() .build() // Construct the BillingClient instance with the purchases updated listener, // pending purchases support, and the billing choice params. val billingClient = BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .enablePendingPurchases(pendingPurchasesParams) .enableBillingProgram(enableBillingProgramParams) .build() // Establish a connection to Google Play val billingResult = suspendCancellableCoroutine<BillingResult> { continuation -> billingClient.startConnection(object : BillingClientStateListener { // Called when the connection setup process completes. override fun onBillingSetupFinished(billingResult: BillingResult) { // Resume the coroutine and pass back the BillingResult to the caller. if (continuation.isActive) { continuation.resume(billingResult) } } // Called if the connection to the Play Store service is dropped. // This prevents the await or suspension point from hanging indefinitely. override fun onBillingServiceDisconnected() { if (continuation.isActive) { continuation.resume( BillingResult.newBuilder() .setResponseCode(BillingResponseCode.SERVICE_DISCONNECTED) .setDebugMessage("Billing service disconnected during connection setup") .build() ) } } }) }
جاوا
EnableBillingProgramParams params = EnableBillingProgramParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .build(); BillingClient billingClient = BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .enablePendingPurchases() .enableBillingProgram(params) .build();تأیید کنید که انتخاب صورتحساب پردازششده توسط توسعهدهنده برای کاربر دردسترس است.
برای بررسی دردسترس بودن برنامه، با
isBillingProgramAvailableAsyncتماس بگیرید، و سپس برای نمایش محصولات دردسترس، باqueryProductDetailsAsyncتماس بگیرید. برای مثال:کاتلین
val (billingResult, billingProgramAvailabilityDetails) = billingClient.isBillingProgramAvailable(BillingProgram.BILLING_CHOICE) if (billingResult.responseCode == BillingResponseCode.OK) { val billingChoiceAvailabilityDetails = billingProgramAvailabilityDetails.billingChoiceAvailabilityDetails if (billingChoiceAvailabilityDetails != null && billingChoiceAvailabilityDetails.choiceScreenType == ChoiceScreenType.DEVELOPER_RENDERED ) { // Billing choice is available. Query products and proceed. // You can inspect details such as: // - billingChoiceAvailabilityDetails.choiceScreenType // - billingChoiceAvailabilityDetails.isExternalLinkAvailable } else { // Fallback to other available programs. } } else { // Fallback to other available programs. }
جاوا
// ... billingClient.isBillingProgramAvailableAsync( BillingProgram.BILLING_CHOICE, (billingResult, billingProgramAvailabilityDetails) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK) { BillingChoiceAvailabilityDetails billingChoiceAvailabilityDetails = billingProgramAvailabilityDetails.getBillingChoiceAvailabilityDetails(); if (billingChoiceAvailabilityDetails != null && billingChoiceAvailabilityDetails.getChoiceScreenType() == ChoiceScreenType.DEVELOPER_RENDERED) { // Billing choice is available. Query products and proceed. // You can inspect details such as: // - billingChoiceAvailabilityDetails.getChoiceScreenType() // - billingChoiceAvailabilityDetails.isExternalLinkAvailable() } else { // Fallback to other available programs. } } else { // Fallback to other available programs. } } );توجه:
billingProgramAvailabilityDetailsبه شما میگوید که صفحه انتخاب صورتحساب پردازششده توسط Google یا صفحه انتخاب صورتحساب پردازششده توسط «توسعهدهنده» دردسترس است یا نه.برای دریافت برنمای «خدمات صورتحساب Play» و اطلاعات وفاداری، روش
getBillingChoiceInfoAsyncرا فراخوانی کنید. برای مثال:کاتلین
// 1. Create the params required for the request val params = GetBillingChoiceInfoParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setPlayBillingChoiceImageLayout(GetBillingChoiceInfoParams.ImageLayout.RECTANGULAR_FOUR_BY_ONE) .build() // 2. Call the suspend method on your billingClient instance val (billingResult, playBillingChoiceInfo) = billingClient.getBillingChoiceInfo(params) if (billingResult.responseCode == BillingResponseCode.OK && playBillingChoiceInfo != null) { // Access the URL of the image associated with the Play Billing Choice val imageUrl = playBillingChoiceInfo.playBillingChoiceImageUrl // Access the Play Loyalty string information, if available val loyaltyInfo = playBillingChoiceInfo.playBillingLoyaltyInfo // Populate your developer-rendered UI elements playBillingLoyaltyTextView.text = loyaltyInfo loadImage(imageUrl, playBillingImageView) } else { // Handle error scenarios }
جاوا
// 1. Create the params required for the request GetBillingChoiceInfoParams params = GetBillingChoiceInfoParams.newBuilder() .setBillingProgram(BillingClient.BillingProgram.BILLING_CHOICE) .setPlayBillingChoiceImageLayout(GetBillingChoiceInfoParams.ImageLayout.RECTANGULAR_FOUR_BY_ONE) .build(); // 2. Call the method asynchronously on your billingClient instance billingClient.getBillingChoiceInfoAsync(params, (billingResult, playBillingChoiceInfo) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK && playBillingChoiceInfo != null) { // Access the URL of the image associated with the Play Billing Choice String imageUrl = playBillingChoiceInfo.getPlayBillingChoiceImageUrl(); // Access the Play Loyalty string information, if available String loyaltyInfo = playBillingChoiceInfo.getPlayBillingLoyaltyInfo(); // Populate your developer-rendered UI elements playBillingLoyaltyTextView.setText(loyaltyInfo); loadImage(imageUrl, playBillingImageView); } else { // Handle error scenarios } });یک کد تراکنش خارجی با DeveloperBillingType تنظیمشده روی IN_APP ایجاد کنید. برای مثال:
کاتلین
// Build the parameters specifying the billing program and that the billing type is IN_APP. val params = BillingProgramReportingDetailsParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setDeveloperBillingType(DeveloperBillingType.IN_APP) .build() // Call the suspending extension function to request the reporting details val (billingResult, billingProgramReportingDetails) = billingClient.createBillingProgramReportingDetails(params) if (billingResult.responseCode != BillingResponseCode.OK) { // Handle failures such as retrying due to network errors. return } // Extract the transaction token from the returned reporting details val transactionToken = billingProgramReportingDetails?.externalTransactionToken // Persist the external transaction token locally. Pass it to // DeveloperBillingOptionParams when launchBillingFlow is called. // It can also be used as part of your external website
جاوا
BillingProgramReportingDetailsParams params = BillingProgramReportingDetailsParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setDeveloperBillingType(DeveloperBillingType.IN_APP) .build(); billingClient.createBillingProgramReportingDetailsAsync( params, new BillingProgramReportingDetailsListener() { @Override public void onCreateBillingProgramReportingDetailsResponse( BillingResult billingResult, @Nullable BillingProgramReportingDetails billingProgramReportingDetails ) { if (billingResult.getResponseCode() != BillingResponseCode.OK) { // Handle failures such as retrying due to network errors. return; } String transactionToken = billingProgramReportingDetails.getExternalTransactionToken(); // Persist the external transaction token locally. Pass it to // DeveloperBillingOptionParams when launchBillingFlow is called. // It can also be used as part of your external website } } );وقتی کاربر روی «خرید» کلیک میکند، با
showBillingProgramInformationDialogتماس بگیرید تا کادر گفتگوی اطلاعات نشان دهید. برای مثال، کادر گفتگوی اطلاعات برای کاربران را ببینید. BillingProgram و transactionToken از مرحله ۴ باید در درخواست تنظیم شود.توجه: کنترل والدین برای کاربران تحت نظارت نمایش داده میشود.
اگر نتیجه مرحله قبلی
OKاست، صفحه انتخاب صورتحساب جایگزین را راهاندازی کنید.انتخاب نوع صورتحساب توسط کاربر را به این صورت مدیریت کنید:
- اگر کاربر «خدمات صورتحساب Play» را انتخاب کرد،
launchBillingFlowرا طبق رهنمودهای استاندارد «خدمات صورتحساب Play» فراخوانی کنید. نتیجه صورتحساب بهPurchasesUpdatedListenerثبتشده در مرحله ۱ برگردانده میشود. - اگر کاربر صورتحساب جایگزین شما را انتخاب کند، باید تراکنش را خودتان انجام دهید و آن را بااستفاده از کد شناسایی تولیدشده در مرحله ۴ به Play گزارش کنید.
- اگر کاربر «خدمات صورتحساب Play» را انتخاب کرد،
مدیریت سناریو 2A
Google صفحه انتخاب را پردازش میکند و صورتحساب جایگزین در خارج برنامه شما مدیریت میشود. برای فعال کردن انتخاب صورتحساب در این سناریو، مراحل زیر را انجام دهید:
هنگام ساختن نمونه BillingClient، با
EnableBillingProgramParamsباenableBillingProgramتماس بگیرید، و سپس اتصال را شروع کنید. برای مثال:کاتلین
// Build the parameters to enable the Billing Choice program and assign the listener // to handle user selection of the developer-provided billing option. val enableBillingProgramParams = EnableBillingProgramParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setDeveloperProvidedBillingListener(developerProvidedBillingListener) .build() // Build the parameters to enable support for pending purchases. val pendingPurchasesParams = PendingPurchasesParams.newBuilder() .enableOneTimeProducts() .build() // Construct the BillingClient instance with the purchases updated listener, // pending purchases support, and the billing choice params. val billingClient = BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .enablePendingPurchases(pendingPurchasesParams) .enableBillingProgram(enableBillingProgramParams) .build() // Establish a connection to Google Play val billingResult = suspendCancellableCoroutine<BillingResult> { continuation -> billingClient.startConnection(object : BillingClientStateListener { // Called when the connection setup process completes. override fun onBillingSetupFinished(billingResult: BillingResult) { // Resume the coroutine and pass back the BillingResult to the caller. if (continuation.isActive) { continuation.resume(billingResult) } } // Called if the connection to the Play Store service is dropped. // This prevents the await or suspension point from hanging indefinitely. override fun onBillingServiceDisconnected() { if (continuation.isActive) { continuation.resume( BillingResult.newBuilder() .setResponseCode(BillingResponseCode.SERVICE_DISCONNECTED) .setDebugMessage("Billing service disconnected during connection setup") .build() ) } } }) }
جاوا
EnableBillingProgramParams params = EnableBillingProgramParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setDeveloperProvidedBillingListener(developerProvidedBillingListener) .build(); BillingClient billingClient = BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .enablePendingPurchases( PendingPurchasesParams.newBuilder() .enableOneTimeProducts() .build() ) .enableBillingProgram(params) .build();دردسترس بودن موارد زیر را درستیسنجی کنید:
- انتخاب روش صدور صورتحساب ارائهشده توسط Google
- پیوند وب خارجی
برای بررسی دردسترس بودن برنامه، با
isBillingProgramAvailableAsyncتماس بگیرید، و سپس برای نمایش محصولات دردسترس، باqueryProductDetailsAsyncتماس بگیرید. برای مثال:کاتلین
// Check the availability of the billing choice program asynchronously using coroutines val (billingResult, billingProgramAvailabilityDetails) = billingClient.isBillingProgramAvailable(BillingProgram.BILLING_CHOICE) // Ensure the billing program query succeeded if (billingResult.responseCode == BillingResponseCode.OK) { // Retrieve the availability details specific to the billing choice program val billingChoiceAvailabilityDetails = billingProgramAvailabilityDetails.billingChoiceAvailabilityDetails // Check if billing choice is available, renders via Google Play, and external link is supported if (billingChoiceAvailabilityDetails != null && billingChoiceAvailabilityDetails.choiceScreenType == ChoiceScreenType.GOOGLE_RENDERED && billingChoiceAvailabilityDetails.isExternalLinkAvailable ) { // Billing choice is available and external transaction links are supported. Query products and proceed. } else { // Fallback to other available programs. } } else { // Fallback to other available programs. }
جاوا
// ... billingClient.isBillingProgramAvailableAsync( BillingProgram.BILLING_CHOICE, (billingResult, billingProgramAvailabilityDetails) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK) { BillingChoiceAvailabilityDetails billingChoiceAvailabilityDetails = billingProgramAvailabilityDetails.getBillingChoiceAvailabilityDetails(); if (billingChoiceAvailabilityDetails != null && billingChoiceAvailabilityDetails.getChoiceScreenType() == ChoiceScreenType.GOOGLE_RENDERED && billingChoiceAvailabilityDetails.isExternalLinkAvailable()) { // Billing choice is available and external transaction links are supported. // Query products and proceed. } else { // Fallback to other available programs. } } else { // Fallback to other available programs. } } );وقتی کاربر قصد خرید را نشان میدهد، با
createBillingProgramReportingDetailsAsyncتماس بگیرید تا رمز تراکنش خارجی ایجاد کنید. برای مثال:کاتلین
// Build the parameters for creating reporting details val params = BillingProgramReportingDetailsParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setDeveloperBillingType(DeveloperBillingType.EXTERNAL_LINK) .build() // Call the suspend function to create billing program reporting details val (billingResult, billingProgramReportingDetails) = billingClient.createBillingProgramReportingDetails(params) // Handle response failure cases if (billingResult.responseCode != BillingResponseCode.OK) { // Handle failures such as retrying due to network errors. return } // Retrieve the external transaction token val transactionToken = billingProgramReportingDetails?.externalTransactionToken // Persist the external transaction token locally. Pass it to // DeveloperBillingOptionParams when launchBillingFlow is called. // It can also be used as part of your external website
جاوا
BillingProgramReportingDetailsParams params = BillingProgramReportingDetailsParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setDeveloperBillingType(DeveloperBillingType.EXTERNAL_LINK) .build(); billingClient.createBillingProgramReportingDetailsAsync( params, new BillingProgramReportingDetailsListener() { @Override public void onCreateBillingProgramReportingDetailsResponse( BillingResult billingResult, @Nullable BillingProgramReportingDetails billingProgramReportingDetails ) { if (billingResult.getResponseCode() != BillingResponseCode.OK) { // Handle failures such as retrying due to network errors. return; } String transactionToken = billingProgramReportingDetails.getExternalTransactionToken(); // Persist the external transaction token locally. Pass it to // DeveloperBillingOptionParams when launchBillingFlow is called. // It can also be used as part of your external website. } } );برای راهاندازی جریان خرید وقتی کاربر روی «خرید» کلیک میکند،
launchBillingFlowرا فراخوانی کنید. اگر انتخاب صورتحساب برای کاربر دردسترس است، موارد زیر را انجام دهید:- از
DeveloperBillingOptionParamsبهBillingFlowParamsبروید. - رمز تراکنش خارجی را از مرحله ۳ به
DeveloperBillingOptionParamsارسال کنید.
برای مثال:
کاتلین
// Build the developer billing option parameters with the external link URI, // the transaction token, and browser/app launch mode. val developerBillingOptionParams = DeveloperBillingOptionParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setLinkUri(Uri.parse("https://www.example.com/external/purchase")) .setExternalTransactionToken(transactionToken) .setLaunchMode( DeveloperBillingOptionParams.LaunchMode.LAUNCH_IN_EXTERNAL_BROWSER_OR_APP ) .build() val billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .enableDeveloperBillingOption(developerBillingOptionParams) .build() val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)
جاوا
DeveloperBillingOptionParams developerBillingOptionParams = DeveloperBillingOptionParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setLinkUri(Uri.parse("https://www.example.com/external/purchase")) .setExternalTransactionToken(transactionToken) .setLaunchMode( DeveloperBillingOptionParams.LaunchMode.LAUNCH_IN_EXTERNAL_BROWSER_OR_APP) .build();توجه: کنترل والدین برای کاربران تحت نظارت نمایش داده میشود.
- از
انتخاب نوع صورتحساب توسط کاربر را به این صورت مدیریت کنید:
- اگر کاربر «خدمات صورتحساب Play» را انتخاب کرد،
launchBillingFlowرا طبق رهنمودهای استاندارد «خدمات صورتحساب Play» فراخوانی کنید. نتیجه صورتحساب بهPurchasesUpdatedListenerثبتشده در مرحله ۱ برگردانده میشود. - اگر کاربر صورتحساب جایگزین شما را انتخاب کند، نتیجه صورتحساب به
DeveloperProvidedBillingListenerثبتشده در مرحله- برگردانده میشود
DeveloperProvidedBillingDetailsبرگشتی حاویexternalTransactionTokenاست که در مرحله ۴ بهDeveloperBillingOptionParamsارسال شده است، درصورتیکه نشان معتبر باشد.
- اگر کاربر «خدمات صورتحساب Play» را انتخاب کرد،
پرداختن به سناریو 2B
توسعهدهنده صفحه انتخاب را ارائه میکند و صورتحساب جایگزین در خارج برنامه مدیریت میشود. برای فعال کردن انتخاب صورتحساب در این سناریو، مراحل زیر را انجام دهید:
تماس با
enableBillingProgramبدونDeveloperProvidedBillingListenerدرEnableBillingProgramParamsهنگام ساختن نمونه BillingClient ، و سپس اتصال را شروع کنید. برای مثال:کاتلین
// Build the parameters to enable the Billing Choice program. val enableBillingProgramParams = EnableBillingProgramParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .build() // Build the parameters to enable support for pending purchases. val pendingPurchasesParams = PendingPurchasesParams.newBuilder() .enableOneTimeProducts() .build() // Construct the BillingClient instance with the purchases updated listener, // pending purchases support, and the billing choice params. val billingClient = BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .enablePendingPurchases(pendingPurchasesParams) .enableBillingProgram(enableBillingProgramParams) .build() // Establish a connection to Google Play val billingResult = suspendCancellableCoroutine<BillingResult> { continuation -> billingClient.startConnection(object : BillingClientStateListener { // Called when the connection setup process completes. override fun onBillingSetupFinished(billingResult: BillingResult) { // Resume the coroutine and pass back the BillingResult to the caller. if (continuation.isActive) { continuation.resume(billingResult) } } // Called if the connection to the Play Store service is dropped. // This prevents the await or suspension point from hanging indefinitely. override fun onBillingServiceDisconnected() { if (continuation.isActive) { continuation.resume( BillingResult.newBuilder() .setResponseCode(BillingResponseCode.SERVICE_DISCONNECTED) .setDebugMessage("Billing service disconnected during connection setup") .build() ) } } }) }
جاوا
EnableBillingProgramParams params = EnableBillingProgramParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .build(); BillingClient billingClient = BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .enablePendingPurchases() .enableBillingProgram(params) .build();دردسترس بودن موارد زیر را درستیسنجی کنید:
- انتخاب روش صدور صورتحساب ارائهشده توسط Google
- پیوند وب خارجی
برای بررسی دردسترس بودن برنامه، با
isBillingProgramAvailableAsyncتماس بگیرید، و سپس برای نمایش محصولات دردسترس، باqueryProductDetailsAsyncتماس بگیرید. برای مثال:کاتلین
// Check the availability of the billing choice program asynchronously using a coroutine val (billingResult, billingProgramAvailabilityDetails) = billingClient.isBillingProgramAvailable(BillingProgram.BILLING_CHOICE) // Ensure the response code is OK if (billingResult.responseCode == BillingResponseCode.OK) { // Retrieve the billing choice availability details val billingChoiceAvailabilityDetails = billingProgramAvailabilityDetails.billingChoiceAvailabilityDetails // Check if billing choice details are available, choice screen is developer-rendered, // and external transaction links are supported. if (billingChoiceAvailabilityDetails != null && billingChoiceAvailabilityDetails.choiceScreenType == ChoiceScreenType.DEVELOPER_RENDERED && billingChoiceAvailabilityDetails.isExternalLinkAvailable ) { // Billing choice is available and external transaction links are supported. // Query products and proceed. } else { // Fallback to other available programs. } } else { // Fallback to other available programs. }
جاوا
// ... billingClient.isBillingProgramAvailableAsync( BillingProgram.BILLING_CHOICE, (billingResult, billingProgramAvailabilityDetails) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK) { BillingChoiceAvailabilityDetails billingChoiceAvailabilityDetails = billingProgramAvailabilityDetails.getBillingChoiceAvailabilityDetails(); if (billingChoiceAvailabilityDetails != null && billingChoiceAvailabilityDetails.getChoiceScreenType() == ChoiceScreenType.DEVELOPER_RENDERED && billingChoiceAvailabilityDetails.isExternalLinkAvailable()) { // Billing choice is available and external transaction links are supported. Query products and proceed. } else { // Fallback to other available programs. } } else { // Fallback to other available programs. } } );برای دریافت برنمای «خدمات صورتحساب Play» و اطلاعات وفاداری، روش
getBillingChoiceInfoAsyncرا فراخوانی کنید.کاتلین
// 1. Create the params required for the request val params = GetBillingChoiceInfoParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setPlayBillingChoiceImageLayout(GetBillingChoiceInfoParams.ImageLayout.RECTANGULAR_FOUR_BY_ONE) .build() // 2. Call the suspend method on your billingClient instance val (billingResult, playBillingChoiceInfo) = billingClient.getBillingChoiceInfo(params) if (billingResult.responseCode == BillingResponseCode.OK && playBillingChoiceInfo != null) { // Access the URL of the image associated with the Play Billing Choice val imageUrl = playBillingChoiceInfo.playBillingChoiceImageUrl // Access the Play Loyalty string information, if available val loyaltyInfo = playBillingChoiceInfo.playBillingLoyaltyInfo // Populate your developer-rendered UI elements playBillingLoyaltyTextView.text = loyaltyInfo loadImage(imageUrl, playBillingImageView) } else { // Handle error scenarios }
جاوا
// 1. Create the params required for the request GetBillingChoiceInfoParams params = GetBillingChoiceInfoParams.newBuilder() .setBillingProgram(BillingClient.BillingProgram.BILLING_CHOICE) .setPlayBillingChoiceImageLayout(GetBillingChoiceInfoParams.ImageLayout.RECTANGULAR_FOUR_BY_ONE) .build(); // 2. Call the method asynchronously on your billingClient instance billingClient.getBillingChoiceInfoAsync(params, (billingResult, playBillingChoiceInfo) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK && playBillingChoiceInfo != null) { // Access the URL of the image associated with the Play Billing Choice String imageUrl = playBillingChoiceInfo.getPlayBillingChoiceImageUrl(); // Access the Play Loyalty string information, if available String loyaltyInfo = playBillingChoiceInfo.getPlayBillingLoyaltyInfo(); // Populate your developer-rendered UI elements playBillingLoyaltyTextView.setText(loyaltyInfo); loadImage(imageUrl, playBillingImageView); } else { // Handle error scenarios } });وقتی کاربر قصد خرید را نشان میدهد، با
createBillingProgramReportingDetailsAsyncتماس بگیرید تا رمز تراکنش خارجی ایجاد کنید. برای مثال:کاتلین
// Build the parameters for creating reporting details val params = BillingProgramReportingDetailsParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setDeveloperBillingType(DeveloperBillingType.EXTERNAL_LINK) .build() // Call the suspend function to create billing program reporting details val (billingResult, billingProgramReportingDetails) = billingClient.createBillingProgramReportingDetails(params) // Handle response failure cases if (billingResult.responseCode != BillingResponseCode.OK) { // Handle failures such as retrying due to network errors. return } // Retrieve the external transaction token val transactionToken = billingProgramReportingDetails?.externalTransactionToken // Persist the external transaction token locally. Pass it to // DeveloperBillingOptionParams when launchBillingFlow is called. // It can also be used as part of your external website
جاوا
BillingProgramReportingDetailsParams params = BillingProgramReportingDetailsParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setDeveloperBillingType(DeveloperBillingType.EXTERNAL_LINK) .build(); billingClient.createBillingProgramReportingDetailsAsync( params, new BillingProgramReportingDetailsListener() { @Override public void onCreateBillingProgramReportingDetailsResponse( BillingResult billingResult, @Nullable BillingProgramReportingDetails billingProgramReportingDetails ) { if (billingResult.getResponseCode() != BillingResponseCode.OK) { // Handle failures such as retrying due to network errors. return; } String transactionToken = billingProgramReportingDetails.getExternalTransactionToken(); // Persist the external transaction token locally. Pass it to // DeveloperBillingOptionParams when launchBillingFlow is called. // It can also be used as part of your external website. } } );وقتی کاربر روی «خرید» کلیک میکند، صفحه انتخاب جایگزین خود را راهاندازی کنید.
انتخاب نوع صورتحساب توسط کاربر را به این صورت مدیریت کنید:
اگر کاربر «خدمات صورتحساب Play» را انتخاب کرد،
launchBillingFlowرا طبق رهنمودهای استاندارد «خدمات صورتحساب Play» فراخوانی کنید. نتیجه صورتحساب بهPurchasesUpdatedListenerثبتشده در مرحله ۱ برگردانده میشود.کنترل والدین برای کاربران تحت نظارت نمایش داده میشود.
اگر کاربر صورتحساب جایگزین شما را انتخاب کرد، launchExternalLink را فراخوانی کنید. برای مثال:
کاتلین
// An activity reference from which the purchase flow will be launched. val params = LaunchExternalLinkParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setExternalTransactionToken(transactionToken) // You can pass along the external transaction token from // BillingProgramReportingDetails as a URL parameter in the URI .setLinkUri(yourLinkUri) .setLinkType(LaunchExternalLinkParams.LinkType.LINK_TO_DIGITAL_CONTENT_OFFER) .setLaunchMode( LaunchExternalLinkParams.LaunchMode.LAUNCH_IN_EXTERNAL_BROWSER_OR_APP ) .build() // Call launchExternalLink with a callback billingClient.launchExternalLink(activity, params) { billingResult -> if (billingResult.responseCode == BillingResponseCode.OK) { // Proceed with the rest of the purchase flow. If the user // purchases an item, be sure to report the transaction to Google // Play. } else { // Handle failures such as retrying due to network errors. } }
جاوا
// An activity reference from which the purchase flow will be launched. Activity activity = ...; LaunchExternalLinkParams params = LaunchExternalLinkParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) // You can pass along the external transaction token from // BillingProgramReportingDetails as a URL parameter in the URI .setLinkUri(yourLinkUri) .setLinkType(LaunchExternalLinkParams.LinkType.LINK_TO_DIGITAL_CONTENT_OFFER) .setLaunchMode( LaunchExternalLinkParams.LaunchMode.LAUNCH_IN_EXTERNAL_BROWSER_OR_APP) .setExternalTransactionToken(transactionToken) .build(); LaunchExternalLinkResponseListener listener = new LaunchExternalLinkResponseListener() { @Override public void onLaunchExternalLinkResponse(BillingResult billingResult) { if (billingResult.getResponseCode() == BillingResponseCode.OK) { // Proceed with the rest of the purchase flow. If the user // purchases an item, be sure to report the transaction to Google // Play. } else { // Handle failures such as retrying due to network errors. } } }; billingClient.launchExternalLink(activity, params, listener);رمز تراکنش خارجی را از مرحله ۴ به
LaunchExternalLinkParamsارسال کنید. اگر OK برگرداند، تراکنش را ادامه دهید و تراکنش را به Google Play گزارش دهید.کنترل والدین برای کاربران تحت نظارت نمایش داده میشود.
انتخاب روش صدور صورتحساب درطول جایگزینی اشتراک
برای جایگزینی اشتراک، صفحه انتخاب کاربر نباید نمایش داده شود زیرا انتخاب کاربر برای خرید اصلی برای ارتقا و تنزل حفظ میشود.
اگر خرید اصلی ازطریق «خدمات صورتحساب Google Play» پردازش شده است، باید با launchBillingFlow تماس بگیرید و اطلاعات استاندارد جایگزینی اشتراک «خدمات صورتحساب Google Play» را ارائه دهید.
بااینحال، اگر خرید اصلی ازطریق سیستم صورتحساب جایگزین پردازش شده باشد، رسیدگی به جایگزینی اشتراکها براساس سناریوها کمی متفاوت است.
جایگزینی اشتراک در «سناریو ۱ الف»
کاربرانی که درخواست ارتقا یا تنزل دارند باید بدون اینکه دوباره تجربه انتخاب کاربر را طی کنند، ازطریق سیستم صورتحساب جایگزین توسعهدهنده اقدام کنند.
برای انجام این کار، وقتی کاربر درخواست ارتقا یا تنزل میدهد، با launchBillingFlow تماس بگیرید. از setOriginalExternalTransactionId در داخل
شیء SubscriptionUpdateParams در پارامترها برای ارائه شناسه
تراکنش خارجی برای خرید اصلی استفاده کنید. این کار صفحه انتخاب کاربر را نمایش نمیدهد، زیرا انتخاب کاربر برای خرید اصلی برای ارتقا و تنزل حفظ میشود. تماس با launchBillingFlow در این مورد
نشان تراکنش خارجی جدیدی برای تراکنشی که میتوانید
از برگشت تماس بازیابی کنید تولید میکند.
کاتلین
// The external transaction ID from the current // alternative billing subscription. val externalTransactionId = "external_transaction_id" val developerBillingOptionParams = DeveloperBillingOptionParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .build() val billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList( listOf( BillingFlowParams.ProductDetailsParams.newBuilder() // Fetched using queryProductDetailsAsync. .setProductDetails(productDetailsNewPlan) // offerIdToken can be found in // ProductDetails=>SubscriptionOfferDetails. .setOfferToken(offerTokenNewPlan) .build() ) ) .setSubscriptionUpdateParams( SubscriptionUpdateParams.newBuilder() .setOriginalExternalTransactionId(externalTransactionId) .build() ) .enableDeveloperBillingOption(developerBillingOptionParams) .build() val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams) // When the user selects the alternative billing flow, // the DeveloperProvidedBillingListener is triggered.
جاوا
// The external transaction ID from the current
// alternative billing subscription.
String externalTransactionId = //... ;
DeveloperBillingOptionParams developerBillingOptionParams =
DeveloperBillingOptionParams.newBuilder()
.setBillingProgram(BillingProgram.BILLING_CHOICE)
.build();
List<ProductDetailsParams> productDetailsParamsList = new ArrayList<>();
productDetailsParamsList.add(
ProductDetailsParams.newBuilder()
// Fetched using queryProductDetailsAsync.
.setProductDetails(productDetailsNewPlan)
// offerIdToken can be found in
// ProductDetails=>SubscriptionOfferDetails
.setOfferToken(offerTokenNewPlan)
.build());
BillingFlowParams billingFlowParams =
BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.setSubscriptionUpdateParams(
SubscriptionUpdateParams.newBuilder()
.setOriginalExternalTransactionId(externalTransactionId)
.build())
.enableDeveloperBillingOption(developerBillingOptionParams)
.build();
BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);
// When the user selects the alternative billing flow,
// the DeveloperProvidedBillingListener is triggered.
وقتی ارتقا یا تنزل تکمیل شد، باید بااستفاده از کد تراکنش خارجی که ازطریق تماس قبلی برای خرید اشتراک جدید دریافت کردهاید، تراکنش جدیدی را گزارش کنید.
جایگزینی اشتراک در «سناریو ۱ب»
در این سناریو، باید یک کد تراکنش خارجی جدید تولید شود. تنها تفاوت با خرید عادی این است که در این سناریو، انتخاب کاربر حفظ میشود و لازم نیست صفحه انتخاب را برای ارتقا یا تنزل نمایش دهید. بااینحال، باید کادر گفتگوی اطلاعات یکبارمصرف و تأییدیه ولی را نشان دهید.
برای نمونه کد یکپارچهسازی، به مرحله ۴ در «سناریو ۱-ب: توسعهدهنده صفحه انتخاب را ارائه میکند و صورتحساب جایگزین در برنامه شما مدیریت میشود» مراجعه کنید.
وقتی ارتقا یا تنزل تکمیل شد، باید بااستفاده از کد تراکنش خارجی که ازطریق تماس قبلی برای خرید اشتراک جدید دریافت کردهاید، تراکنش جدیدی را گزارش کنید.
جایگزینی اشتراک در «سناریو ۲A»
برای اشتراکهایی که در ابتدا ازطریق وبسایت توسعهدهنده یا برنامه پرداخت پساز انتخاب کاربر خریداری شدهاند، کاربرانی که درخواست ارتقا یا تنزل میدهند باید ازطریق وبسایت توسعهدهنده یا برنامه پرداخت اقدام کنند و دوباره تجربه انتخاب کاربر را طی نکنند.
برای انجام این کار، وقتی کاربر درخواست ارتقا یا تنزل میدهد، با launchBillingFlow تماس بگیرید. بهجای مشخص کردن پارامترهای دیگر در شیء
SubscriptionUpdateParams، از setOriginalExternalTransactionId استفاده کنید و
شناسه تراکنش خارجی را برای خرید اصلی ارائه دهید.
DeveloperBillingOptionParams نیز باید در این تماس ارائه شود. این کار صفحه انتخاب کاربر را نمایش نمیدهد، زیرا انتخاب کاربر برای خرید اصلی برای ارتقا و تنزل حفظ میشود. برای مثال:
کاتلین
val externalTransactionId = "external_transaction_id" // 1. Construct DeveloperBillingOptionParams indicating the billing program val developerBillingOptionParams = DeveloperBillingOptionParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .build() // 2. Build BillingFlowParams combining DeveloperBillingOptionParams and SubscriptionUpdateParams val billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList( listOf( BillingFlowParams.ProductDetailsParams.newBuilder() // Fetched using queryProductDetailsAsync. .setProductDetails(productDetailsNewPlan) // offerIdToken can be found in ProductDetails=>SubscriptionOfferDetails. .setOfferToken(offerTokenNewPlan) .build() ) ) .setSubscriptionUpdateParams( SubscriptionUpdateParams.newBuilder() .setOriginalExternalTransactionId(externalTransactionId) .build() ) .enableDeveloperBillingOption(developerBillingOptionParams) .build()
جاوا
String externalTransactionId = //... ;
// 1. Construct DeveloperBillingOptionParams indicating the billing program
DeveloperBillingOptionParams developerBillingOptionParams =
DeveloperBillingOptionParams.newBuilder()
.setBillingProgram(BillingClient.BillingProgram.BILLING_CHOICE)
.build();
// 2. Add ProductDetailsParams
List productDetailsParamsList = new ArrayList<>();
productDetailsParamsList.add(
ProductDetailsParams.newBuilder()
// Fetched using queryProductDetailsAsync.
.setProductDetails(productDetailsNewPlan)
// offerIdToken can be found in ProductDetails=>SubscriptionOfferDetails
.setOfferToken(offerTokenNewPlan)
.build());
// 3. Build BillingFlowParams combining DeveloperBillingOptionParams and SubscriptionUpdateParams
BillingFlowParams billingFlowParams =
BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.setSubscriptionUpdateParams(
SubscriptionUpdateParams.newBuilder()
.setOriginalExternalTransactionId(externalTransactionId)
.build())
.enableDeveloperBillingOption(developerBillingOptionParams)
.build();
همچنین باید کد تراکنش خارجی جدیدی تولید کنید. برای مثال:
کاتلین
val params = BillingProgramReportingDetailsParams.newBuilder() .setBillingProgram(BillingProgram.BILLING_CHOICE) .setDeveloperBillingType(DeveloperBillingType.EXTERNAL_LINK) .build() val (billingResult, billingProgramReportingDetails) = billingClient.createBillingProgramReportingDetails(params) if (billingResult.responseCode != BillingResponseCode.OK) { // Handle failures such as retrying due to network errors. return } val externalTransactionToken = billingProgramReportingDetails?.externalTransactionToken // Persist the external transaction token locally. Pass it to // the external website using DeveloperBillingOptionParams when // launchBillingFlow is called.
جاوا
BillingProgramReportingDetailsParams params =
BillingProgramReportingDetailsParams.newBuilder()
.setBillingProgram(BillingProgram.BILLING_CHOICE)
.setDeveloperBillingType(DeveloperBillingType.EXTERNAL_LINK)
.build();
billingClient.createBillingProgramReportingDetailsAsync(
params,
new BillingProgramReportingDetailsListener() {
@Override
public void onCreateBillingProgramReportingDetailsResponse(
BillingResult billingResult,
@Nullable BillingProgramReportingDetails billingProgramReportingDetails) {
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
// Handle failures such as retrying due to network errors.
return;
}
String transactionToken =
billingProgramReportingDetails.getExternalTransactionToken();
// Persist the external transaction token locally. Pass it to
// the external website using DeveloperBillingOptionParams when
// launchBillingFlow is called.
}
});
پساز تولید کردن کد جدید، باید روش launchBillingFlow
را برای راهاندازی جریان خرید فراخوانی کنید.
وقتی ارتقا یا تنزل تکمیل شد، باید بااستفاده از کد تراکنش خارجی که ازطریق تماس قبلی برای خرید اشتراک جدید دریافت کردهاید، تراکنش جدیدی را گزارش کنید.
جایگزینی اشتراک در «سناریو ۲B»
مراحل مدیریت جایگزینی اشتراک در این سناریو مشابه مراحل توضیحدادهشده در جایگزینی اشتراک در سناریو 2A است. تنها تفاوت این است که پساز تولید کردن کد تراکنش، بهجای فراخوانی روش launchBillingFlow، باید launchExternalLink را فراخوانی کنید تا کادر گفتگوی سلب مسئولیت پیوند خروجی نشان داده شود. در این سناریو، انتخاب کاربر حفظ میشود
و لازم نیست صفحه انتخاب را برای ارتقا یا تنزل نشان دهید.
برای نمونه کد یکپارچهسازی، مرحله ۶ در «سناریو ۲-ب: توسعهدهنده صفحه انتخاب را ارائه میکند و صورتحساب جایگزین در برنامه شما مدیریت میشود» را ببینید.
وقتی ارتقا یا تنزل تکمیل شد، باید بااستفاده از کد تراکنش خارجی که ازطریق تماس قبلی برای خرید اشتراک جدید دریافت کردهاید، تراکنش جدیدی را گزارش کنید.