Giới Thiệu
Việc tích hợp tính năng mua hàng trong ứng dụng Unity là không thể tránh khỏi. Mua hàng trong ứng dụng có ba loại: consumable, non-consumable, and subscription.
Trong bài viết này, Mình sẽ giải thích cách triển khai loại consumable bằng Unity IAP.
Sự chuẩn bị
1. Unity IAP (In App Purchase) package
Chọn [Window] > [Package Manager] từ thanh menu và một cửa sổ sẽ xuất hiện.
Thay đổi menu thả xuống ở trên cùng bên trái thành [Packages: Unity Registry], chọn [In App Purchasing] và nhấp vào Cài đặt ở dưới cùng bên phải để hoàn tất cài đặt.
Ngoài ra, bạn có thể cài đặt nó bằng cách thêm trực tiếp vào Packages/manifest.json
File manifest.json
{
"com.unity.purchasing": "4.9.4",
...
}
2. Đăng ký App/In-App Purchase Product
Để kiểm tra thanh toán trên thiết bị thực tế, bạn cũng cần phải thiết lập
- Đăng ký với Chương trình nhà phát triển Apple/Google Play Console
- Tạo ứng dụng trên Chương trình nhà phát triển Apple/Google Play Console
- Đăng ký thanh toán trong ứng dụng trên Chương trình nhà phát triển Apple/Google Play Console
Bạn có thể tham khảo ở đây:
Google Play Console: https://www.youtube.com/watch?v=58FsBs4YD2I
Apple: https://www.youtube.com/watch?v=oPAZ3Y2dujc
Thực hiện
1. Consumables
Đầu tiên, consumable in-app purchase? Nó đề cập đến phí cho những vật phẩm sẽ biến mất sau khi chúng được sử dụng, chẳng hạn như tiền trong game để quay vật phẩm.
Trong phần 1.1, trước tiên chúng tôi sẽ giải thích quy trình khi thực hiện thành công giao dịch mua hàng tiêu dùng trong ứng dụng.
1.1 Hện thống thông thường
Sơ đồ tuần tự bên dưới là quy trình vận hành thanh toán tiêu hao ở chế độ bình thường (khi có thể thanh toán mà không gặp bất kỳ sự cố nào).
Việc thanh toán được thực hiện từ trên xuống dưới và phần màu xanh có nghĩa là giao dịch thanh toán đã được thành công.
Dưới đây là ví dụ triển khai tối thiểu về thanh toán tiêu dùng.
File IAP.cs
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.Purchasing.Extension;
using UnityEngine.Networking;
public class IAP : MonoBehaviour, IDetailedStoreListener
{
private IStoreController m_StoreController;
public const string coinId = "com.example.myapp.coin";
private void Start()
{
InitializePurchasing();
}
// a. Xử lý khởi tạo
public void InitializePurchasing()
{
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
builder.AddProduct(coinId, ProductType.Consumable);
UnityPurchasing.Initialize(this, builder);
}
// b. Danh sách sản phẩm
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
Debug.Log("Quá trình khởi tạo hoàn tất");
m_StoreController = controller;
}
// c. Yêu cầu thanh Toán
public void BuyCoin()
{
m_StoreController.InitiatePurchase(coinId);
}
// d. Biên nhận mua hàng
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
var product = args.purchasedProduct;
StartCoroutine(SendReceiptToServer(product));
// Vì biên nhận chưa được gửi ở đây, hãy trả lại Đang chờ xử lý và tạo giao dịch.
return PurchaseProcessingResult.Pending;
}
// e. Gửi biên nhật
private IEnumerator SendReceiptToServer(Product product)
{
// Gửi biên nhận đến server (giả)
var request = UnityWebRequest.Get("https://example.com/receipt/");
yield return request.SendWebRequest();
if (request.responseCode < 300)
{
Debug.Log($"Scucess: {product.definition.id}");
m_StoreController.ConfirmPendingPurchase(product);
}
}
public void OnInitializeFailed(InitializationFailureReason error)
{
OnInitializeFailed(error, null);
}
public void OnInitializeFailed(InitializationFailureReason error, string message)
{
Debug.Log($"Lỗi khởi tạo: {error}");
}
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
Debug.Log($"Mua hàng thất bại Product:'{product.definition.id}', PurchaseFailureReason:{failureReason}");
}
}
- Khi InitializePurchasing() được thực thi và quá trình khởi tạo hoàn tất, phương thức OnInitialized() sẽ tự động được gọi
- Khi BuyCoin() được thực thi và quá trình tính phí hoàn tất, ProcessPurchase(args) sẽ tự động được gọi (OnInitialized và ProcessPurchase là interface của IDetailedStoreListener)
- Sau đó, biên nhận được gửi đến server trong SendReceiptToServer thành công thì lúc đó giao dịch được hoàn tất bằng cách gọi m_StoreController.ConfirmPendingPurchase(product)
Về giao dịch trong IAP
Như đã giải thích trước đó, sau khi quá trình mua hoàn tất, ProcessPurchase() sẽ được gọi.
Nếu phương thức này trả về PurchaseProcessingResult.Complete, giao dịch sẽ được hoàn tất
nhưng nếu nó trả về PurchaseProcessingResult.Pending, giao dịch sẽ được đăng ở trạng thái chưa hoàn tất.
Giao dịch có thể được hoàn thành bằng cách gọi m_StoreController.ConfirmPendingPurchase(product).
1.2 Hệ thống bất thường
Có thể thấy trong sơ đồ trình tự, cửa hàng sẽ gửi cho ứng dụng một biên lai để chứng minh việc mua hàng, biên lai này sẽ gửi đến máy chủ để lấy hàng.
Nếu người dùng không gửi biên lai và xảy ra lỗi mạng hoặc tác vụ bị hủy ngay sau khi thanh toán hoàn tất, biên lai sẽ không được gửi đến máy chủ. Điều này dẫn đến việc người dùng không nhận được vật phẩm mà họ đã trả tiền, gây ra vấn đề nghiêm trọng. Do đó, cần đảm bảo rằng biên lai được gửi thành công đến máy chủ, vì nó chứa thông tin chi tiết về giao dịch.
Dưới đây là sơ đồ trình tự của trường hợp ứng dụng bị buộc chấm dứt. Tôi đã nhận được biên lai mua hàng và đóng ứng dụng trước khi gửi đến máy chủ. Tuy nhiên , vì nó đang quay trở lại nên một giao dịch đã được thực hiện.
Người dùng đã nhận được biên lai mua hàng và đóng ứng dụng trước khi gửi nó đến máy chủ.
Tuy nhiên, vì PurchaseProcessingResult.Pending được trả về nên một giao dịch sẽ được đăng. Sau đó, ProcessPurchase() sẽ được gọi lại vào lần tiếp theo khi ứng dụng khởi động và quá trình khởi tạo hoàn tất. Điều này được lặp lại cho đến khi giao dịch hoàn tất, đảm bảo rằng biên nhận được gửi đến máy chủ.
1.3 Convenience store payment (Google Play Store)
Những phần trên là giải thích về thanh toán tiêu dùng chung, nhưng nếu bạn muốn phát hành nó trên Cửa hàng Google Play, bạn sẽ cần hỗ trợ riêng thanh toán tại cửa hàng tiện lợi. Còn được gọi là mua trả chậm (Deferred Purchase).
Trình tự thanh toán tại cửa hàng tiện lợi như sau: Trong trường hợp thanh toán tại Convenience store payment, mã thanh toán chỉ được cấp khi bạn nhấn nút bắt đầu thanh toán, nhưng việc thanh toán không được hoàn tất ngay tại chỗ. ProcessPurchase được gọi khi người dùng khởi chạy ứng dụng sau khi thanh toán Convenience store payment. Bằng cách thêm code bên dưới, bạn có thể phát hiện thời điểm bắt đầu thanh toán Convenience store payment.
File IAP.cs
public void InitializePurchasing()
{
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
builder.Configure<IGooglePlayConfiguration>().SetDeferredPurchaseListener(
product => Debug.Log("Đã bắt đầu thanh toán Convenience store payment"));
}
Ngoài ra, khi bắt đầu thanh toán Convenience store payment, các biên lai chưa thanh toán có thể được gửi đến ProcessPurchase, do đó cần phải loại bỏ quy trình này. (Biên lai sẽ được gửi riêng sau khi thanh toán hoàn tất)
File IAP.cs
IGooglePlayStoreExtensions m_GooglePlayStoreExtensions;
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
Debug.Log("Quá trình khởi tạo hoàn tất");
m_StoreController = controller;
m_GooglePlayStoreExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();
}
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
var product = args.purchasedProduct;
if (m_GooglePlayStoreExtensions.IsPurchasedProductDeferred(product))
{
Debug.Log("Biên nhận thanh toán tại cửa hàng tiện lợi chưa hoàn thành. Bỏ qua nó vì không cần gửi nó đến máy chủ");
return PurchaseProcessingResult.Pending;
}
}
Link Tham khảo:
https://docs.unity3d.com/Manual/UnityIAPProcessingPurchases.html
https://dev.rbcafe.com/unity/unity-5.3.3/en/Manual/UnityIAPProcessingPurchases.html