Bỏ qua, tới nội dung
App Store
5 phút đọc

Bẫy IAP đầu tiên trên App Store Connect: khi rút bản nộp

Rút một bản nộp đang mang in-app purchase đầu tiên có thể để lại trạng thái Ready for Review ma và lỗi 500. Thứ tự làm đúng, từng bước một.

Dao Van Thuong
Mobile & Fullstack Engineer
Read in English
Ảnh bìa: hai hàng bước nộp app, hàng trên nét đứt và gãy, hàng dưới dẫn tới Submit
Trong bài này
  1. Vì sao IAP đầu tiên khác các lần sau
  2. Chuyện gì đã xảy ra với Linkeeper
  3. Thứ tự chạy được
  4. Subscription group có thêm một luật nữa
  5. Thay build đang nằm trong review
  6. Rút ra

Ngày 30/9 mình nộp Linkeeper 1.2.0 kèm Linkeeper Unlimited, in-app purchase đầu tiên của app. Chưa tới một ngày, mình đã rút bản nộp đó khỏi review hai lần để thay build mới hơn vào. Lần thứ hai, App Store Connect bắt đầu trả về lỗi HTTP 500, còn IAP thì nằm ở trạng thái "Ready for Review" mà không thuộc về bản nộp nào mình nhìn thấy được.

Cuối cùng không mất gì. Lỗi 500 hết sau khoảng nửa tiếng, IAP tự về trạng thái cũ, và version cùng IAP được nộp chung. Nhưng nửa tiếng đó rất dễ bị bấm loạn cho tệ thêm, và mấy quy tắc gây ra nó thì không ai nói trước cho tới khi mình đụng phải. Bài này là thứ tự mình làm bây giờ, gom lại từ Linkeeper, từ lần ra mắt subscription đầu tiên của Lockboxy, và ghi ra cho app tiếp theo có subscription group.

Vì sao IAP đầu tiên khác các lần sau

Khi app đã có in-app purchase được duyệt, một version mới chỉ là bản nộp một item: version và build của nó. Các bản cập nhật sau này của Lockboxy nộp đúng như vậy, vì subscription group và các sản phẩm đã live từ trước.

IAP đầu tiên thì khác. App Store Connect ghi rõ ngay trên trang subscription: in-app purchase đầu tiên phải được nộp cùng một app version mới. Bạn chọn nó ở mục "In-App Purchases and Subscriptions" trên trang version, và nó được review cùng build đó. Checklist billing của Linkeeper cũng có đúng dòng này: gắn IAP vào version bật billing.

Cái bẫy nằm ở chỗ "nộp cùng version" là thuộc tính của một bản nộp cụ thể. Rút bản nộp đó khỏi review thì cả version lẫn IAP cùng rớt ra, và IAP không tự động đi theo version sang bản nộp kế tiếp.

Chuyện gì đã xảy ra với Linkeeper

Trình tự, theo ghi chú release trong repo:

Lúc nàoChuyện gì
30/9Nộp 1.2.0 (build 5) kèm Linkeeper Unlimited, review screenshot của nó và phần cập nhật App Privacy
30/9Rút khỏi hàng chờ (hiện thành Developer Rejected). Cùng version record đó đổi tên thành 1.3.0 với build 7, IAP nộp lại theo
1/10Rút build 7, thay bằng build 8 có thêm quảng cáo rewarded tự chọn
1/10Thêm version vào draft bị lỗi 500 khoảng 30 phút, IAP hiện trạng thái "Ready for Review" ma
1/10Cả hai tự hết; version và IAP được nộp chung

Chỗ hỏng là những gì mình làm trong nửa tiếng đó. Lúc đang có một draft submission rỗng, mình bấm "Add for Review" ngay trên trang của IAP. IAP nhảy sang "Ready for Review" mà không có version nào đi cùng. Xoá draft đi cũng không gỡ được: IAP vẫn kẹt ở đó, nút "Add for Review" bị disable, không còn gì để bấm. Khoảng 30 phút sau nó tự quay về "Prepare for Submission", và luồng bình thường chạy lại được.

Mình cũng thử nộp IAP qua App Store Connect API thay vì giao diện. Với non-consumable đầu tiên thì API từ chối: nó phải đi cùng một version, và endpoint nộp in-app purchase không nhận nó một mình. Đường duy nhất là bấm trên trang IAP trong giao diện.

Thứ tự chạy được

Hai hàng bước: thứ tự hỏng là draft rỗng, Add for Review trên IAP, xoá draft; thứ tự chạy là version vào draft, Add for Review trên trang IAP chọn đúng draft đó, rồi submit cùng lúc
Version trước, IAP sau, cùng một draft
  1. Đưa version vào draft trước. Trên trang version, gắn build mới xong thì chọn Add for Review, để có một draft submission đã chứa sẵn version.
  2. Sau đó mới vào trang của IAP và bấm Add for Review. App Store Connect hỏi dùng draft nào; chọn đúng draft đang chứa version.
  3. Kiểm số item trong draft, rồi Submit.

Nếu bước 1 trả về lỗi 500 thì dừng lại. Đừng tạo draft thứ hai, đừng thêm IAP vào một draft rỗng, và đừng xoá draft đang chứa IAP. Trong trường hợp của mình, bấm gì trong lúc lỗi cũng không giúp được, và chính một cú bấm trong đó là thứ làm IAP bị kẹt. Đợi khoảng 30 phút rồi thử lại đúng bước đó thì được.

Subscription group có thêm một luật nữa

IAP của Linkeeper chỉ là một non-consumable. Lockboxy thì ra mắt với một subscription group (gói tháng và gói năm) cộng một gói lifetime, và chính nó dạy mình nửa còn lại của chuyện này.

Bản nộp đầu của Lockboxy bị từ chối vì thiếu link Terms of Use (guideline 3.1.2(c)), và ba IAP bị từ chối theo. Mình sửa description rồi nộp lại với build mới hơn, nhưng bản nộp lại chỉ có đúng một item: version. Các IAP vẫn nằm ở Developer Rejected và sẽ không được review. Sau khi huỷ bản nộp đó, thử Add for Review riêng subscription group thì bị báo hai lỗi: subscription group mới phải được nộp kèm một auto-renewable subscription nằm trong group đó, và phải thêm một app version cho nền tảng.

Cách sửa là một draft chứa mọi thứ mới: version, subscription group, từng subscription, và gói lifetime. Năm item, một lần Submit.

Bốn ô trong cùng một draft: app version kèm build, subscription group, từng subscription, và gói lifetime; bên dưới ghi chú nộp riêng group thì bị từ chối, nộp riêng version thì IAP bị bỏ lại
Lần nộp đầu có group thì cần đủ cả

Đây là dạng quan trọng nhất với Baton, app theo dõi em bé của mình (baton.io.vn). Gói Plus của nó là một subscription năm trong group, có free trial, cộng thêm gói mở khoá lifetime. Phần paywall mình viết ở các pattern paywall với RevenueCat; phần nộp app là checklist này.

Thay build đang nằm trong review

Có lúc build đang review phải đổi: Lockboxy từng có một build trong hàng chờ thiếu bản sửa mình cần, còn build 8 của Linkeeper thêm một tính năng mình muốn có trong cùng bản phát hành. App Store Connect không cho đổi build của một version đã nộp ngay tại chỗ, nên thay build lúc nào cũng phải đi qua bước rút khỏi review.

Sáu bước: upload build mới, rút version khỏi review, đổi build trên cùng version record, đưa version vào draft, thêm mọi IAP đầu tiên gồm group và từng subscription, rồi đếm và submit
Thay build mà vẫn giữ IAP đầu tiên
  1. Upload build mới và đợi processing xong. Làm việc này trước khi rút bất cứ thứ gì, để version nằm ngoài hàng chờ càng ngắn càng tốt.
  2. Rút version khỏi review. Nó rớt về Developer Rejected, và các IAP đầu tiên nộp cùng nó cũng vậy.
  3. Đổi build trên chính version record đó. Linkeeper giữ nguyên record, chỉ đổi tên từ 1.2.0 thành 1.3.0; không cần tạo record mới.
  4. Đưa version vào draft. Nếu bị lỗi 500 thì đợi, đừng ứng biến.
  5. Thêm mọi IAP đầu tiên vào draft đó từ trang riêng của từng cái: gói non-consumable, và với app subscription thì cả group lẫn từng subscription trong group.
  6. Đếm item rồi Submit. Lần ra mắt đầu có một group, hai subscription và một gói lifetime là năm item. Chỉ có một item nghĩa là IAP đã bị bỏ lại.

Cái giá phải trả là vị trí trong hàng chờ. Rút version khỏi review thì thời gian chờ tính lại từ đầu, nên mình chỉ làm khi build trong hàng chờ sẽ đưa tới người dùng thứ mình không muốn họ nhận.

Rút ra

  • IAP đầu tiên sống bên trong một bản nộp. Rút bản nộp đó là phải dựng lại từ đầu, version và IAP cùng nhau.
  • Version trước, IAP sau, cùng một draft. Đừng bao giờ Add for Review trên IAP khi draft duy nhất đang rỗng.
  • Subscription group không bao giờ đi một mình. Nó cần ít nhất một subscription của chính nó và một app version trong cùng draft.
  • Đếm item trước khi bấm Submit. Đây là bước kiểm rẻ nhất để IAP không bị bỏ lại ở Developer Rejected.
  • Gặp 500 thì đợi. Trường hợp của mình tự hết sau khoảng 30 phút; tạo thêm draft hay xoá draft chỉ thêm một IAP bị kẹt.

Linkeeper ở linkeeper.io.vn, Lockboxy ở lockboxy.io.vn, và cả bảy app đều có ở apps.vanthuongdao.id.vn. Bài App Store trước nói về một kiểu bị từ chối khác: để giá trong screenshot và guideline 2.3.7.

  • #App Store Connect
  • #In-App Purchases
  • #Linkeeper
  • #Lockboxy
  • #Baton
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.