BlogRight arrowTutorialRight ArrowMua trong ứng dụng Android, phần 1: cấu hình và thêm vào dự án
BlogRight arrowTutorialRight ArrowMua trong ứng dụng Android, phần 1: cấu hình và thêm vào dự án

Mua trong ứng dụng Android, phần 1: cấu hình và thêm vào dự án

Mua trong ứng dụng Android, phần 1: cấu hình và thêm vào dự án
Listen to the episode
Mua trong ứng dụng Android, phần 1: cấu hình và thêm vào dự án

Mua trong ứng dụng (in-app purchase), đặc biệt là gói đăng ký (subscription), là phương thức kiếm tiền từ ứng dụng phổ biến nhất. Nhìn chung, một mặt, gói đăng ký cho phép nhà phát triển đầu tư vào phát triển nội dung và sản phẩm, mặt khác, gói đăng ký giúp người dùng có được ứng dụng với chất lượng cao hơn. Mua trong ứng dụng sẽ chịu 30% phí hoa hồng, nhưng nếu người dùng đã đăng ký một năm trở lên hoặc ứng dụng có doanh thu hàng năm dưới 1 triệu USD, phí hoa hồng phải chịu là 15%.

Trong bài viết này, chúng tôi sẽ giải thích cách:  

  • Tạo một sản phẩm trong Google Play Console;
  • Cấu hình gói đăng ký: cách chỉ định thời gian, giá cả và dùng thử; 
  • Thêm danh sách sản phẩm trong một ứng dụng. 
⭐️ Download our guide on in-app techniques which will make in-app purchases in your app perfect

Tạo gói đăng ký

Trước khi chúng ta bắt đầu, đảm bảo rằng bạn:

  1. tài khoản nhà phát triển trên Google Play.
  2. Đã ký tất cả thoả thuận và sẵn sàng bắt đầu làm việc.  

Bây giờ, hãy bắt tay vào việc và tạo sản phẩm đầu tiên của chúng ta.  

Đổi sang tài khoản nhà phát triển của bạn và chọn ứng dụng.

Sau đó, trong menu bên trái, tìm phần Sản phẩm, chọn Subscriptions  và nhấp vào Create a Subscription

Sau đó, chúng ta sẽ thấy bộ cấu hình gói đăng ký. Sau đây là một số điểm quan trọng.

  1. Tạo ID sẽ được sử dụng trong ứng dụng. Nên thêm kỳ đăng ký hoặc thông tin hữu dụng khác vào ID, nhờ đó, bạn có thể tạo ra sản phẩm theo một phong cách và làm cho việc phân tích thống kê bán hàng trở nên dễ dàng hơn.
  2. Tên gói đăng ký mà người dùng sẽ nhìn thấy trong cửa hàng.  
  3. Mô tả gói đăng ký. Người dùng cũng sẽ thấy thông tin này.  

Cuộn xuống và chọn kỳ đăng ký. Trong trường hợp của chúng ta – khoảng thời gian này là một tuần. Đặt mức giá.  

Thông thường, bạn đặt mức giá bằng đơn vị tiền tệ của tài khoản cơ bản và hệ thống sẽ tự động đổi giá. Nhưng bạn cũng có thể chỉnh sửa giá cho một quốc gia cụ thể theo cách thủ công.  

Vui lòng chú ý rằng Google hiển thị thuế cho mọi quốc gia. Điều này thật tuyệt vời, App Store Connect không có chức năng này.  

Cuộn xuống và chọn (nếu cần): 

  1. Thời gian dùng thử miễn phí.
  2. Giá ưu đãi mặt hàng mới, là một loại ưu đãi cho kỳ thanh toán đầu tiên.  
  3. Thời gian gia hạn. Nếu người dùng gặp vấn đề với thanh toán, bạn vẫn có thể cấp cho họ quyền truy cập cao cấp cho một số ngày.
  4. Một cơ hội để đăng ký lại từ Play Store, không phải từ ứng dụng, sau khi hủy. 

So sánh quy trình mua trong Play Console và App Store Connect

Tuy kiếm tiền bằng gói đăng ký trên iOS hiệu quả hơn nhưng bảng quản trị của Play Console lại tiện lợi hơn, được sắp xếp và địa phương hóa tốt hơn và hoạt động nhanh hơn.

Quy trình tạo sản phẩm được thiết kế đơn giản nhất có thể. Chúng tôi chia sẻ về cách tạo sản phẩm trên iOS tại đây.

Lấy danh sách sản phẩm trong một ứng dụng

Sau khi đã tạo sản phẩm, hãy tiếp tục tạo cấu trúc chấp nhận và xử lý giao dịch mua. Nhìn chung, quy trình như sau:   

  1. Thêm Billing Library.
  2. Phát triển một lớp để tương tác với sản phẩm trên Google Play.
  3. Triển khai tất cả phương thức để xử lý mua.  
  4. Thêm xác nhận máy chủ của đơn hàng.  
  5. Thu thập phân tích.

Trong phần này, hãy cùng xem xét kỹ hơn hai điểm đầu tiên.  

Thêm Billing Library vào một dự án:

implementation "com.android.billingclient:billing:4.0.0"

Tại thời điểm viết bài, phiên bản mới nhất là 4.0.0. Bạn có thể thay bằng bất kỳ phiên bản nào khác ở bất kỳ thời điểm nào.  

Hãy tạo một lớp wrapper về logic tương tác với Google Play và khởi chạy BillingClient từ Billing Library trong lớp đó. Hãy gọi lớp này là BillingClientWrapper.

Lớp này sẽ triển khai giao diện PurchasesUpdatedListener. Bây giờ, chúng ta sẽ ghi đè một phương thức cho nó – onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?) – phương thức này cần thiết ngay sau khi giao dịch mua được thực hiện nhưng chúng ta sẽ mô tả quy trình triển khai trong bài viết tiếp theo. 

import android.content.Context
import com.android.billingclient.api.*

class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {

   private val billingClient = BillingClient
       .newBuilder(context)
       .enablePendingPurchases()
       .setListener(this)
       .build()

   override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList?) {
       // here come callbacks about new purchases
   }
}

Google khuyến nghị không nên có nhiều hơn một kết nối đang hoạt động giữa BillingClient và Google Play để tránh thực thi nhiều lần hàm callback về giao dịch mua được thực hiện. Theo đó, bạn chỉ nên có một BillingClient duy nhất trong một lớp singleton. Lớp trong ví dụ không phải là một singleton nhưng chúng ta có thể dùng dependency injection (ví dụ: với sự trợ giúp của Dagger hoặc Koin) theo cách này, cho phép chỉ một trường hợp được tồn tại ở một thời điểm đơn nhất. 

Để đưa ra bất kỳ yêu cầu nào với Billing Library, BillingClient phải có một kết nối đang hoạt động với Google Play tại thời điểm khi yêu cầu được đưa ra, nhưng kết nối đôi khi cũng có thể bị ngắt. Để tiện dụng, hãy viết một wrapper cho phép chúng ta tạo bất kỳ yêu cầu nào nhưng chỉ khi kết nối đang hoạt động.   

Để lấy sản phẩm, chúng ta cần ID mà chúng ta đã cài đặt trong thị trường. Nhưng như vậy vẫn chưa đủ cho một yêu cầu, chúng ta cũng cần loại sản phẩm (gói đăng ký hoặc mua một lần) đó là lý do tại sao chúng ta có thể lấy danh sách sản phẩm tổng hợp bằng cách “kết hợp” kết quả của hai yêu cầu.

Start for free

Convenient in-app purchases infrastructure.

Adapty SDK has it all:
— server-side purchase validation,
— all side cases covered,
— simple implementation.

Start for free

Yêu cầu đối với các sản phẩm không đồng bộ nên chúng ta cần một hàm callback để cung cấp danh sách sản phẩm hoặc trả về mô hình lỗi. Khi xảy ra lỗi, Billing Library trả về một trong các mã BillingResponseCodes cũng như debugMessage. Hãy tạo giao diện callback và mô hình cho lỗi:  

interface OnQueryProductsListener {
  fun onSuccess(products: List < SkuDetails > )
  fun onFailure(error: Error)
}

class Error(val responseCode: Int, val debugMessage: String)

Đây là mã cho phương thức riêng tư để lấy dữ liệu về một loại sản phẩm cụ thể và phương thức công khai sẽ “kết hợp” kết quả của hai yêu cầu và cung cấp cho người dùng danh sách sản phẩm cuối cùng hoặc hiển thị thông báo lỗi.

fun queryProducts(listener: OnQueryProductsListener) {
   val skusList = listOf("premium_sub_month", "premium_sub_year", "some_inapp")

   queryProductsForType(
       skusList,
       BillingClient.SkuType.SUBS
   ) { billingResult, skuDetailsList ->
       if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
           val products = skuDetailsList ?: mutableListOf()
           queryProductsForType(
               skusList,
               BillingClient.SkuType.INAPP
           ) { billingResult, skuDetailsList ->
               if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                   products.addAll(skuDetailsList ?: listOf())
                   listener.onSuccess(products)
               } else {
                   listener.onFailure(
                       Error(billingResult.responseCode, billingResult.debugMessage)
                   )
               }
           }
       } else {
           listener.onFailure(
               Error(billingResult.responseCode, billingResult.debugMessage)
           )
       }
   }
}

private fun queryProductsForType(
   skusList: List,
   @BillingClient.SkuType type: String,
   listener: SkuDetailsResponseListener
) {
   onConnected {
       billingClient.querySkuDetailsAsync(
           SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
           listener
       )
   }
}

Nhờ đó, chúng ta có thông tin giá trị về sản phẩm (SkuDetails) chứa tên, giá, loại sản phẩm đã địa phương hóa cũng như kỳ thanh toán và thông tin về giá ưu đãi mặt hàng mới và thời gian dùng thử (nếu có cho người dùng này) của gói đăng ký. Lớp cuối cùng có dạng như sau:   

import android.content.Context
import com.android.billingclient.api.*
class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {
   interface OnQueryProductsListener {
       fun onSuccess(products: List<SkuDetails>)
       fun onFailure(error: Error)
   }
   class Error(val responseCode: Int, val debugMessage: String)
   private val billingClient = BillingClient
       .newBuilder(context)
       .enablePendingPurchases()
       .setListener(this)
       .build()
   fun queryProducts(listener: OnQueryProductsListener) {
       val skusList = listOf("premium_sub_month", "premium_sub_year", "some_inapp")
       queryProductsForType(
           skusList,
           BillingClient.SkuType.SUBS
       ) { billingResult, skuDetailsList ->
           if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
               val products = skuDetailsList ?: mutableListOf()
               queryProductsForType(
                   skusList,
                   BillingClient.SkuType.INAPP
               ) { billingResult, skuDetailsList ->
                   if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                       products.addAll(skuDetailsList ?: listOf())
                       listener.onSuccess(products)
                   } else {
                       listener.onFailure(
                           Error(billingResult.responseCode, billingResult.debugMessage)
                       )
                   }
               }
           } else {
               listener.onFailure(
                   Error(billingResult.responseCode, billingResult.debugMessage)
               )
           }
       }
   }
   private fun queryProductsForType(
       skusList: List<String>,
       @BillingClient.SkuType type: String,
       listener: SkuDetailsResponseListener
   ) {
       onConnected {
           billingClient.querySkuDetailsAsync(
               SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
               listener
           )
       }
   }
   private fun onConnected(block: () -> Unit) {
       billingClient.startConnection(object : BillingClientStateListener {
           override fun onBillingSetupFinished(billingResult: BillingResult) {
               block()
           }
           override fun onBillingServiceDisconnected() {
               // Try to restart the connection on the next request to
               // Google Play by calling the startConnection() method.
           }
       })
   }
   override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList?) {
       // here come callbacks about new purchases
   }
}

Bài viết cho hôm nay đã hết. Trong bài viết tiếp theo, chúng tôi sẽ cho bạn biết về cách triển khai mua hàng, thử nghiệm và xử lý lỗi.

Further reading

Adapty October update 
Adapty October update 
November 4, 2020
3 min read
Understanding Churn: 3 Reasons Why Your Customers Leave
Understanding Churn: 3 Reasons Why Your Customers Leave
December 4, 2020
10 min read
New in Adapty: paywall metrics, menu redesign, last sent event time, and your stars on GitHub
New in Adapty: paywall metrics, menu redesign, last sent event time, and your stars on GitHub
October 11, 2022
6 min read