Bỏ qua, tới nội dung
Kiếm tiền
6 phút đọc

Vì sao RevenueCat báo 143 khách khi chỉ 17 người tải app

RevenueCat đếm app user ID chứ không đếm người, đếm doanh thu gộp chứ không phải tiền về tay. Đọc đúng số của Lockboxy, và chuyển bản debug sang Test Store.

Dao Van Thuong
Mobile & Fullstack Engineer
Read in English
Ảnh bìa: dashboard RevenueCat báo 143 khách cạnh App Store Connect báo 17 lượt tải
Trong bài này
  1. "Customer" của RevenueCat là gì
  2. 143 từ đâu ra
  3. Doanh thu không phải proceeds
  4. Cách sửa: bản debug nói chuyện với Test Store
  5. Nửa còn lại của cách sửa là một cái form
  6. Giờ mình đọc dashboard thế nào
  7. Checklist

Tuần này mình mở liền hai dashboard của Lockboxy, app két riêng tư của mình.

RevenueCat, 28 ngày qua: 143 active customer, 136 new customer, doanh thu $4.15, 2 subscription đang chạy.

App Store Connect, 90 ngày tính tới 30/09: 17 lượt tải lần đầu, 4 lượt tải lại, 38 lượt cập nhật, $2 proceeds, 2 giao dịch in-app purchase.

Một khoảng thời gian ngắn hơn mà số "khách" gấp tám lần số lượt tải của khoảng dài hơn. Và doanh thu gấp đôi số tiền thật sự về tới tài khoản ngân hàng. Không dashboard nào sai cả. Chúng đếm những thứ khác nhau, và với một app indie nhỏ thì độ chênh đủ lớn để đánh lừa bạn về chuyện app có đang tăng trưởng hay không. Bài này giải thích các con số đó thật ra nghĩa là gì, và thay đổi một dòng code giúp việc test của chính mình không còn thổi phồng chúng.

"Customer" của RevenueCat là gì

RevenueCat không biết gì về con người. Nó biết app user ID. Khi app gọi Purchases.configure() mà không truyền ID, đúng kiểu app không có tài khoản hay làm, SDK tự sinh một ID ẩn danh dạng $RCAnonymousID:… và lưu trên máy. ID đó chính là customer.

Lockboxy không có đăng nhập. Nó là két offline, và đó là cả ý nghĩa của nó. Nên mọi customer của nó đều ẩn danh, và luật của ID ẩn danh quyết định con số:

  • Mỗi lần cài là một customer mới. Xoá app rồi cài lại, ID cũ mất theo dữ liệu của app. SDK tạo ID mới.
  • Cập nhật từ một bản chưa có SDK tạo ra một customer mới ở lần mở đầu tiên của bản mới, vì trước đó chưa từng có ID.
  • Mọi build chạy bằng key production đều được tính. Kể cả simulator, TestFlight, và các thiết bị App Review dùng.

"New customers" là số ID RevenueCat thấy lần đầu trong khoảng thời gian đó. "Active customers" là số ID có liên lạc với RevenueCat trong khoảng đó, mới hay cũ đều tính. Không con số nào là "số người đã tải app".

143 từ đâu ra

Đặt hai dashboard cạnh nhau thì khoảng chênh đến từ nhiều nguồn. Mình không tách chính xác được 143, vì ID ẩn danh không mang nhãn nói nó từ đâu tới, nhưng nguồn nào cũng có thật:

17 + 4 + 38 của App Store Connect cạnh các nguồn sinh ra customer ID của RevenueCat, cộng lại thành 143
Lượt tải đếm người theo Apple ID; customer đếm app user ID theo lần cài và theo build
  1. Lượt cài thật. 17 lượt tải lần đầu và 4 lượt tải lại là 21 lượt cài, tức ít nhất 21 ID. Tải lại vẫn là người cũ, nhưng là customer mới.
  2. Cập nhật. 38 lượt cập nhật trong App Store Connect. Ai cập nhật từ một bản có trước khi gắn SDK RevenueCat sẽ thành customer mới ở lần mở đầu tiên sau đó. Ai cập nhật từ bản đã có SDK thì hiện ra là active, không phải new.
  3. Chính mình test. Đây là nguồn mình sửa được, và nó cộng dồn rất nhanh. Mọi bản debug trên simulator đều cấu hình RevenueCat bằng key App Store production. Mọi flow Maestro và mọi lượt UX audit cũng vậy. Mỗi simulator mới, mỗi lần cài lại, mỗi thiết bị test bị xoá trắng là một customer production mới.
  4. TestFlight và App Review. Các build này cũng dùng key production, vì chúng chính là build sẽ ship. Mỗi lượt cài của tester và mỗi thiết bị review thêm một ID.

Để ý thêm là khoảng thời gian không khớp nhau. 143 của RevenueCat phủ 28 ngày, còn 21 lượt cài của App Store Connect phủ 90 ngày. Khoảng chênh thật còn rộng hơn con số nhìn thấy.

Doanh thu không phải proceeds

Số tiền cũng lệch nhau, gấp đôi: $4.15 so với $2. Ngược lại, số giao dịch thì khớp. RevenueCat có 2 subscription đang chạy và App Store Connect có 2 in-app purchase. Nên không ai mất đơn nào cả. Khác nhau ở chỗ mỗi dashboard gọi con số nào là "tiền".

Con số doanh thu mặc định của RevenueCat là gộp: khách đã trả bao nhiêu, quy ra đô, trước khi Apple lấy phần của họ. Proceeds của App Store Connect là số Apple sẽ trả bạn sau hai khoản trừ:

  • Hoa hồng của Apple, mặc định 30%, hoặc 15% nếu ở Small Business Program (nói ở dưới).
  • Thuế. Ở nhiều storefront, giá khách thấy đã gồm VAT hoặc loại thuế tương tự, và Apple trừ nó ra trước khi tính phần của bạn.
Từ $4.15 gộp trên RevenueCat tới $2 proceeds trên App Store Connect: hoa hồng và thuế bị trừ trước
Cùng hai giao dịch, hai định nghĩa về tiền

Ở mức $4.15 thì hai đô chỉ là sai số làm tròn. Ở một tháng thật thì đó là khác biệt giữa một kế hoạch kinh doanh và một thú vui. Nếu bạn đọc RevenueCat để quyết xem app có tự nuôi được nó không, hãy nhẩm cắt doanh thu đi khoảng một nửa với app nhỏ ở mức phí chuẩn, rồi kiểm lại báo cáo thanh toán trong App Store Connect, vì đó mới là con số thật sự về tay.

Cách sửa: bản debug nói chuyện với Test Store

Mình không cấm được TestFlight hay App Review dùng key production, vì chúng phải test đúng build thật. Nhưng simulator và các flow tự động hoàn toàn không cần tới project production. RevenueCat có Test Store: một store riêng cho mỗi project với public key riêng (tiền tố test_), sản phẩm riêng, không dính tới StoreKit, và giao dịch hoàn tất ngay trong app mà không cần Apple ID.

Nên config billing của Lockboxy giờ chọn key theo loại build:

ts
/** `true` in debug builds only: billing talks to RevenueCat's Test Store. */
export const USE_REVENUECAT_TEST_STORE: boolean = __DEV__;

export const REVENUECAT_API_KEY: string = USE_REVENUECAT_TEST_STORE
  ? REVENUECAT_TEST_STORE_API_KEY // 'test_…', both platforms
  : Platform.OS === 'android'
    ? REVENUECAT_API_KEY_BY_PLATFORM.android // 'goog_…'
    : REVENUECAT_API_KEY_BY_PLATFORM.ios; // 'appl_…'

Chỗ dễ sót là product ID. Sản phẩm trên App Store là com.vanthuongdao.calcvault.premium.monthly và tương tự, còn sản phẩm của Test Store chỉ là monthly, yearly và lifetime. Mọi chỗ tra cứu trong app, từ package trên paywall, điều kiện intro offer tới "gói nào đang chạy", đều dựa vào product ID. Nên bảng map ID phải đổi theo key:

ts
export const REVENUECAT_TEST_STORE_PRODUCT_IDS = {
  monthly: 'monthly',
  yearly: 'yearly',
  lifetime: 'lifetime',
} as const;

export const REVENUECAT_PRODUCT_IDS = USE_REVENUECAT_TEST_STORE
  ? REVENUECAT_TEST_STORE_PRODUCT_IDS
  : Platform.OS === 'android'
    ? REVENUECAT_PRODUCT_IDS_BY_PLATFORM.android
    : REVENUECAT_PRODUCT_IDS_BY_PLATFORM.ios;

Sản phẩm của Test Store nằm trong cùng các package của offering default và cấp cùng entitlement premium, nên mọi thứ phía trên file config không cần biết mình đang nói chuyện với store nào. Hàm guard có sẵn kiểm tiền tố key (appl_ trên iOS, goog_ trên Android, để bắt lỗi nhét nhầm key của store kia vào build) giờ chấp nhận test_ ở bản debug.

Test xong thì rút ra ba chi tiết:

  • Bản release không đổi gì. TestFlight và App Store dùng đúng key và đường product ID như cũ. Một unit test khẳng định key Test Store không bao giờ xuất hiện trong cấu hình release.
  • Test Store không có promotional offer hay offer code. Exit offer của Lockboxy có một biến thể dùng promotional offer, như mình kể ở bài Pattern paywall với RevenueCat. Ở bản debug nó đơn giản là ra "không có offer", trường hợp code vốn đã xử lý.
  • Giao dịch sandbox StoreKit thật giờ cần bản Release hoặc TestFlight. Mình ghi điều này lên đầu runbook sandbox, vì lần tới muốn test giao dịch sandbox trên simulator thì chắc chắn mình đã quên.

Thay đổi này lên TestFlight hôm nay cùng Lockboxy 1.2.13 (47). Từ giờ project production chỉ thấy lượt cài thật, TestFlight và review.

Nửa còn lại của cách sửa là một cái form

Dòng hoa hồng đáng có hành động riêng. App Store Small Business Program của Apple giảm hoa hồng từ 30% xuống 15% cho developer có tổng proceeds từ mọi app dưới 1 triệu đô một năm. Với dev indie thì đó là cả tài khoản.

Nó không tự động. Bạn đăng ký trên trang developer, khai báo các tài khoản developer liên quan nếu có, và mức phí giảm áp dụng sau khi Apple chấp nhận, không tính ngược về trước. Nếu app của bạn có bán gì đó mà chưa làm việc này, đây là mười phút đáng giá nhất trong bài.

Giờ mình đọc dashboard thế nào

  • Đếm giao dịch, không đếm customer. Subscription đang chạy, trial và giao dịch mang cùng một nghĩa trên RevenueCat lẫn App Store Connect. Số customer thì không, nhất là với app không có tài khoản.
  • So cùng khoảng thời gian. Đặt hai dashboard về cùng khoảng ngày trước khi so bất cứ gì.
  • Coi "new customers" là lượt cài cộng nhiễu. Với app ẩn danh, nó bám theo lượt cài, lượt cài lại và build, không phải con người.
  • Xem proceeds khi nói về tiền. Doanh thu của RevenueCat để nhìn xu hướng. Proceeds và payments của App Store Connect mới là tiền tiêu được.

Nếu bạn mới cấu hình in-app purchase lần đầu, bài bẫy IAP đầu tiên trên App Store Connect đáng đọc trước khi nộp.

Checklist

  • Nhớ rằng customer của RevenueCat là app user ID. Không có đăng nhập thì mỗi lần cài, cài lại và mỗi build test đều tạo ID mới.
  • Chờ sẵn việc các lượt cập nhật từ bản chưa có SDK hiện ra thành customer mới.
  • Cho bản debug (__DEV__) dùng key Test Store của RevenueCat, và đổi bảng map product ID cùng lúc với key.
  • Giữ bản release ở key production, và viết test để key Test Store không lọt vào được.
  • Nhớ Test Store không có promotional offer hay offer code, và dùng TestFlight cho giao dịch sandbox thật.
  • So doanh thu gộp của RevenueCat với proceeds của App Store Connect, trừ hoa hồng và thuế, trong cùng khoảng thời gian.
  • Đăng ký App Store Small Business Program nếu đủ điều kiện. Là 15% thay vì 30%, và chỉ áp dụng sau khi được chấp nhận.
  • #Lockboxy
  • #RevenueCat
  • #App Store Connect
  • #Subscriptions
Chia sẻXLinkedInFacebook
Dao Van Thuong

Kỹ sư mobile và fullstack ở TP. Hồ Chí Minh. Mình tự xây và phát hành các app iOS indie — Lockboxy, Linkeeper, Minivid, Ringsy, Talkzy, Baton và Stampzy.