Trong quá trình kiểm thử IAP trước khi phát hành, chúng tôi gặp một vấn đề rất điển hình nhưng cực kỳ khó hiểu:
- Người dùng bấm “Tiếp tục mua”
- Nút chuyển sang trạng thái “Đang xử lý”
- Vài giây sau quay lại “Tiếp tục mua”
- Không xuất hiện cửa sổ xác nhận mua của App Store
Bài viết này ghi lại toàn bộ hành trình điều tra, các log quan trọng, nguyên nhân gốc và chiến lược sửa lỗi có thể áp dụng ngay trước khi lên production.
1. Hiện tượng
Danh sách sản phẩm ở màn hình mua tải bình thường, nhưng sau khi bấm mua:
purchase()được gọi- Không có hộp thoại hệ thống nào xuất hiện
- UI cuối cùng quay về trạng thái chưa mua
Thoạt nhìn giống như “luồng mua chưa hề chạy”, nhưng log cho thấy câu chuyện khác.
2. Kiểm tra vòng đầu: luồng mua có thực sự chạy không?
Chúng tôi bổ sung log ở mọi điểm quan trọng của luồng mua:
- Tham số lúc bấm nút
- Điểm vào
purchaseSelectedProduct()và các nhánhguard - Trước/sau
product.purchase() Transaction.updatesrefreshEntitlements()Transaction.currentEntitlementsTransaction.latest(for:)
Kết quả:
purchase()trả về.success- Xác thực transaction thành công và
transaction.finish()cũng được gọi - Nhưng
entitledIDsvẫn rỗng
Điều này cho thấy: vấn đề không phải “mua không bắt đầu”, mà là “sau khi mua, entitlement không được nhận diện”.
3. Các phát hiện then chốt sau khi đào sâu
Khi tăng độ chi tiết log, xuất hiện các tín hiệu quyết định:
- Số vòng lặp của
Transaction.currentEntitlementslà0 Transaction.latest(for: monthly)có bản ghi hợp lệ nhưng đã hết hạn- Quan trọng hơn: trước khi mua có transaction cũ trong
Transaction.unfinished purchase()hiện tại trả về transaction từ chuỗi cũ, không phải từ luồng mua tương tác mới
Điều này giải thích vì sao “không có popup”:
- StoreKit ưu tiên trả về transaction cũ còn có thể xử lý (unfinished / historical chain)
- Code của bạn gọi
finish()cho transaction đó - Request trông “thành công”, nhưng không đi qua luồng xác nhận mua mới mà người dùng kỳ vọng
4. Tóm tắt nguyên nhân gốc
Đây không phải lỗi đơn điểm, mà là sự chồng lấp của hai yếu tố:
- Unfinished transactions can thiệp vào luồng mua
- Chuỗi subscription sandbox đang ở trạng thái hết hạn (dù có
latest transaction, chưa chắc là entitlement đang active)
Hành vi cuối cùng: không popup, thành công bề ngoài, entitlement rỗng.
5. Chiến lược khắc phục (đã kiểm chứng thực tế)
5.1 Chủ động dọn unfinished transactions trước khi mua
Khi app khởi động và trước khi bấm mua, quét Transaction.unfinished rồi gọi finish() cho các transaction đã verified để tránh transaction cũ “cướp” luồng mua mới.
5.2 Giữ và tăng cường log chẩn đoán (khuyến nghị trước khi phát hành)
Nên giữ các log này đến sát thời điểm release:
- Snapshot của
unfinished transaction subscription status(state, renewal info)- Số vòng lặp của
currentEntitlements - Chi tiết transaction/expiration từ
latest(for:)
5.3 Dùng kiểm tra entitlement theo hai kênh (tùy chọn tăng độ chắc chắn)
Nếu ở một số môi trường currentEntitlements tạm thời rỗng, có thể dùng latest(for:) làm fallback (nhưng vẫn phải xác thực trạng thái active).
6. Checklist IAP trước khi phát hành
- Trước mỗi lần test, kiểm tra trạng thái subscription của tài khoản Sandbox (đã hết hạn hay chưa)
- Log số lượng
unfinishedở các đường đi quan trọng - Test 3 kịch bản: mua lần đầu / gia hạn sau khi hết hạn / khôi phục mua hàng
- Kiểm tra tính nhất quán giữa
currentEntitlementsvàlatest - Nếu gặp “không popup”, hãy kiểm tra trước xem có rơi vào transaction path cũ không
7. Kết luận
“Bấm mua nhưng không hiện popup” không nhất thiết là lỗi UI hay lỗi timing gọi hàm.
Trong StoreKit 2, trạng thái transaction, hàng đợi unfinished và vòng đời subscription đều ảnh hưởng trực tiếp đến hành vi cuối cùng.
Bổ sung các log và bước dọn dẹp này trước khi phát hành sẽ giảm đáng kể các lỗi IAP khó tái hiện trên production.