Tất cả bài viết
Deep diveĐọc trong 8 phút

Thu Hồi Quyền Truy Cập Sau Khi Hoàn Tiền: Bước Mà Apple và Google Không Tự Làm Thay Bạn

Apple và Google đều có thể xử lý một khoản hoàn tiền trong khi khách hàng vẫn giữ nguyên quyền sử dụng sản phẩm đã mua. Dưới đây là thời điểm chính xác quyền truy cập được thu hồi tự động, khi nào máy chủ của bạn phải tự làm việc đó, và thông báo duy nhất mà hầu hết các tích hợp chưa từng xử lý.

Một chiếc điện thoại thông minh phát sáng trên bàn vào ban đêm, minh họa khoảnh khắc quyền truy cập vào một giao dịch mua đã được hoàn tiền vẫn chưa bị thu hồi

Điểm chính

  • Trên Google Play, việc hoàn tiền một đơn hàng không tự động xóa quyền truy cập của người dùng. Quyền truy cập chỉ bị thu hồi khi mục "Remove entitlement" được chọn trong Play Console, hoặc khi lệnh gọi API có tham số revoke=true, điều không phải là mặc định.
  • Một khoản hoàn tiền được thực hiện mà không kèm tùy chọn revoke sẽ không bao giờ xuất hiện trong Voided Purchases API. Một tác vụ thu hồi chỉ đọc nguồn dữ liệu đó sẽ âm thầm bỏ sót toàn bộ những đơn hàng như vậy.
  • Apple có thể tự động thu hồi giấy phép đối với non-consumable và subscription, nhưng một consumable đã được xem là đã giao ngay khi nó được sử dụng, nên StoreKit không có cơ chế nào để thu hồi nó. Máy chủ của bạn phải tự trừ số dư đó.
  • Apple cũng có thể hủy chính khoản hoàn tiền mà mình đã cấp. Thông báo REFUND_REVERSED nghĩa là một khoản hoàn tiền đã được cấp trước đó bị đảo ngược, revocationDate trở về null, và bất kỳ quyền truy cập hay số dư nào bạn đã xóa đều phải được khôi phục.
  • Trường voidedReason của Google mang chín giá trị, từ remorse và accidental_purchase cho đến fraud, friendly_fraud, và unacknowledged_purchase, vì vậy cùng một nguồn dữ liệu báo cho bạn biết cần thu hồi quyền truy cập cũng cho bạn biết khoản thu hồi nào là gian lận và khoản nào là lỗi trong chính tích hợp của bạn.
  • Mỗi ngày một giao dịch mua đã hoàn tiền vẫn còn giữ quyền truy cập là một ngày bạn phải trả chi phí compute, storage và các lệnh gọi API phía sau nó, cho số tiền mà bạn đã trả lại.

Chấp thuận hoàn tiền và thu hồi quyền truy cập là hai bước tách biệt, và chỉ một trong hai bước đó diễn ra tự động. Các nhà phát triển cho rằng một khoản hoàn tiền đã xử lý đồng nghĩa với việc quyền truy cập đã bị thu hồi đều sai trên cả hai nền tảng lớn, theo những cách khác nhau và vì những lý do khác nhau. Biết chính xác nơi tự động hóa dừng lại chính là điều cần thiết để thu hồi quyền truy cập sau một khoản hoàn tiền, thay vì phát hiện ra nhiều tháng sau rằng điều đó chưa từng xảy ra.

Vì sao hoàn tiền không mặc định thu hồi quyền truy cập

Cả Apple và Google đều xem tiền và quyền truy cập là hai hoạt động khác nhau. Phía tiền bạc là chính sách của cửa hàng, một khi khoản hoàn tiền được duyệt, nó là quyết định cuối cùng và sổ sách được cập nhật. Phía quyền truy cập là tùy chọn, hoặc là việc của nhà phát triển, hoặc cả hai, tùy thuộc vào nền tảng và loại giao dịch mua. Không cửa hàng nào mặc định cho rằng bạn muốn quyền truy cập biến mất ngay khi khoản hoàn tiền hoàn tất, vì rất nhiều trường hợp hợp lệ (hoàn tiền một phần, tín dụng thiện chí, cử chỉ hỗ trợ khách hàng) vốn không nhằm mục đích lấy lại sản phẩm.

Google Play: ô đánh dấu mà không ai để ý

Trong Play Console, hoàn tiền một đơn hàng là một biểu mẫu gồm hai phần, chọn số tiền hoàn, rồi quyết định riêng biệt có đánh dấu "Remove entitlement" hay không. Để trống ô đó là lựa chọn mặc định mà hầu hết nhân viên hỗ trợ thực hiện, vì phần lớn yêu cầu hoàn tiền là cử chỉ thiện chí, nơi khách hàng vẫn giữ những gì họ đã mua. Vấn đề là không có gì trên màn hình xác nhận hoàn tiền cho bạn biết rằng đây cũng là cách một khách hàng vẫn giữ được subscription hoặc vật phẩm trong ứng dụng sau một khoản hoàn tiền mà bạn muốn là quyết định cuối cùng.

Đường dẫn API cũng mắc phải cạm bẫy tương tự

Lựa chọn tương tự cũng tồn tại trong mã nguồn. Endpoint orders.refund nhận một tham số truy vấn revoke không bắt buộc, khi giá trị đó là true, quyền truy cập vào vật phẩm bị chấm dứt ngay lập tức, và đối với một subscription định kỳ, mọi khoản thanh toán trong tương lai cũng bị hủy theo. Nếu bỏ qua tham số này, giá trị mặc định sẽ là false, khoản hoàn tiền vẫn được ghi nhận, bản ghi giao dịch mua được cập nhật, và người dùng vẫn tiếp tục sử dụng sản phẩm y như trước.

Hành độngQuyền truy cập có bị xóa không?Có xuất hiện trong Voided Purchases API không?
Hoàn tiền qua Play Console, "Remove entitlement" không được đánh dấuKhôngKhông
Hoàn tiền qua Play Console, "Remove entitlement" được đánh dấuCó, ngay lập tức
API orders.refund, tham số revoke bị bỏ qua (mặc định false)KhôngKhông
API orders.refund, revoke=trueCó, ngay lập tức

Chính hàng cuối cùng đó là điều khiến các tích hợp mắc bẫy. Voided Purchases API tồn tại để bạn có thể xây dựng một pipeline thu hồi mà không cần kiểm tra Play Console thủ công, nhưng nó chỉ liệt kê những đơn hàng thực sự bị hủy kèm thu hồi. Một khoản hoàn tiền được xử lý mà không có ô đánh dấu đó, hoặc không có revoke=true, vốn được thiết kế để vô hình với nguồn dữ liệu đó, đây không phải là lỗi.

Một dãy máy chủ với một đèn báo trạng thái màu đỏ giữa các hàng đèn xanh, minh họa cho một quyết định thu hồi âm thầm

Đọc trường voidedReason Khi một giao dịch mua thực sự xuất hiện là đã hủy, trường voidedReason cho bạn biết lý do, theo thang chín giá trị: other, remorse, not_received, defective, accidental_purchase, fraud, friendly_fraud, chargeback, và unacknowledged_purchase. Đó không chỉ đơn thuần là ghi sổ. Remorse và accidental_purchase là những khoản hoàn tiền thông thường. Fraud, friendly_fraud, và chargeback là những giá trị đáng để chuyển đến hàng đợi xét duyệt gian lận thay vì một quy trình thu hồi rồi bỏ qua đơn giản, vì cùng một tài khoản đó về mặt thống kê có nhiều khả năng lặp lại hành vi này hơn.

Có một chi tiết vận hành quan trọng nếu bạn xây dựng trực tiếp dựa trên nguồn dữ liệu này: purchases.voidedpurchases.list bị giới hạn ở mức 6.000 truy vấn mỗi ngày và 30 truy vấn trong bất kỳ khoảng thời gian 30 giây nào. Một thiết kế thăm dò liên tục trong một vòng lặp gấp gáp sẽ nhanh chóng chạm ngưỡng đó. Mô hình được khuyến nghị là kéo dữ liệu theo lịch trình với pagination token, không phải thăm dò liên tục.

Apple: App Store có thể thu hồi giấy phép, nhưng không thể với consumable

App Store Server Notifications V2 của Apple gửi một thông báo REFUND ngay khi khoản hoàn tiền hoàn tất, áp dụng như nhau cho consumable, non-consumable, subscription auto-renewable, và subscription non-renewing. Điều gì xảy ra sau thông báo đó hoàn toàn phụ thuộc vào loại giao dịch mua, vì cơ chế thu hồi riêng của StoreKit chỉ bao phủ hai trong bốn loại đó.

Vì sao consumable là ngoại lệ

Một non-consumable hoặc một subscription có một quyền sở hữu cố định mà hệ thống của Apple có thể tắt đi. Một consumable thì không, một khi đã được sử dụng, nó được xem là đã giao, và không còn gì trong StoreKit để thu hồi. Nếu một khách hàng mua 500 coins, tiêu hết chúng, rồi được hoàn tiền, số coins đó đã biến mất theo quan điểm của Apple. Sổ sách duy nhất còn ghi nhận chúng là sổ sách của bạn. Thông báo REFUND là tín hiệu duy nhất để bạn tìm original transaction ID tương ứng và trừ số dư đó khỏi hồ sơ của chính mình; không có gì ở phía Apple sẽ làm việc đó thay bạn.

Thông báoÝ nghĩaMáy chủ của bạn phải làm gì
REFUNDApple đã hoàn tiền cho một giao dịch mua consumable, non-consumable, auto-renewable, hoặc non-renewingThu hồi ngay quyền truy cập non-consumable và subscription; tự trừ số dư consumable
REFUND_DECLINEDApple từ chối yêu cầu hoàn tiền của khách hàngKhông cần làm gì. Quyền truy cập chưa từng gặp rủi ro
REFUND_REVERSEDApple đảo ngược một khoản hoàn tiền mà mình đã cấp trước đóKhôi phục lại bất kỳ quyền truy cập hoặc số dư nào mà thông báo REFUND đã khiến bạn xóa
CONSUMPTION_REQUESTApple muốn bằng chứng của bạn trước khi quyết định về một khoản hoàn tiềnPhản hồi trong vòng 12 giờ; thông báo này xuất hiện trước một REFUND, không phải sau

Thông báo mà hầu như không ai xử lý

REFUND_REVERSED là thông báo mà hầu hết các tích hợp bỏ qua hoàn toàn, vì nó mô tả việc Apple hủy chính quyết định của mình, một trường hợp mà ít nhà phát triển thiết kế sẵn ngay từ đầu. Khi thông báo này đến, revocationDate trên giao dịch trở về null, theo quan điểm của Apple, giao dịch mua đó lại hợp lệ. Nếu máy chủ của bạn đã xóa một entitlement, hủy một bản ghi subscription, hoặc đưa số dư consumable về 0 khi REFUND ban đầu đến, thì REFUND_REVERSED chính là tín hiệu để khôi phục lại tất cả.

Cách khắc phục khoảng trống này, từng bước một

  • Trên Google Play, đặt revoke=true làm mặc định cho mọi luồng hoàn tiền, dù đi qua Play Console hay API, trừ khi có một trường hợp hỗ trợ cụ thể yêu cầu khách hàng được giữ lại quyền truy cập.
  • Thăm dò purchases.voidedpurchases.list theo lịch trình với pagination token, không phải trong một vòng lặp gấp gáp, và giữ dưới mức 6.000 yêu cầu mỗi ngày và 30 yêu cầu trong mỗi 30 giây.
  • Chuyển các giá trị voidedReason là fraud, friendly_fraud, và chargeback đến một hàng đợi xét duyệt thay vì thu hồi rồi đóng lại tự động.
  • Trên Apple, đăng ký App Store Server Notifications V2 và thu hồi quyền truy cập non-consumable cùng subscription ngay khi một thông báo REFUND nêu tên chúng.
  • Lưu original transaction ID cho mỗi giao dịch mua consumable để một thông báo REFUND có thể tìm ra chính xác số dư cần trừ mà không cần tra cứu thủ công.
  • Xử lý REFUND_REVERSED một cách rõ ràng và ghi lại mỗi lần thu hồi theo transaction hoặc order ID của nó, để một lần đảo ngược có thể khôi phục chính xác những gì đã bị xóa.

Cái giá thực sự của khoảng trống này

Tất cả những điều này không phải là chuyện công bằng với khách hàng, mà là chuyện điều gì vẫn tiếp tục vận hành sau khi tiền đã mất. Một subscription đã hoàn tiền nhưng vẫn tiếp tục gọi API, lưu trữ dữ liệu, và tiêu tốn compute không phải là một vấn đề đạo đức, đó là một khoản chi phí, bạn đang trả tiền hạ tầng cho một giao dịch mà bạn đã hoàn lại doanh thu. Các cửa hàng không theo dõi chi phí đó thay bạn, vì từ phía họ, khoản hoàn tiền đã hoàn tất ngay khi tiền được chuyển đi. Phía quyền truy cập hoàn toàn là hóa đơn mà bạn phải tự kiểm soát.

Đây cũng là lý do RefundHalt xem việc thu hồi là một phần của pipeline hoàn tiền, chứ không phải một suy nghĩ muộn màng, một sự kiện REFUND và một thay đổi quyền truy cập được ghi nhận như một hành động duy nhất, và một sự kiện REFUND_REVERSED tự động khôi phục trạng thái thay vì chờ ai đó nhận ra khách hàng vẫn đang bị khóa khỏi thứ mà Apple đã trả lại.

Câu hỏi thường gặp

Việc hoàn tiền một giao dịch mua trên Google Play có tự động thu hồi quyền truy cập không?
Không. Quyền truy cập chỉ bị xóa nếu "Remove entitlement" được đánh dấu trong Play Console hoặc lệnh gọi API orders.refund có kèm revoke=true. Không cái nào trong hai điều này là mặc định, vì vậy một khoản hoàn tiền có thể được ghi nhận trong khi khách hàng vẫn tiếp tục sử dụng sản phẩm.
Điều gì xảy ra khi một giao dịch mua trong ứng dụng dạng consumable được hoàn tiền trên App Store?
Apple gửi một thông báo REFUND, nhưng StoreKit không có cách nào để thu hồi một consumable vì nó được xem là đã giao ngay khi được sử dụng. Máy chủ của nhà phát triển phải tìm giao dịch gốc và trừ số dư theo cách thủ công.
Thông báo REFUND_REVERSED là gì và nó có cần được xử lý đặc biệt không?
REFUND_REVERSED nghĩa là Apple đã đảo ngược một khoản hoàn tiền mà mình đã cấp trước đó; revocationDate trở về null và giao dịch mua đó lại hợp lệ. Bất kỳ quyền truy cập hay số dư nào bị REFUND ban đầu xóa đều phải được khôi phục, và hầu hết các tích hợp chưa từng lắng nghe thông báo này.
Vì sao một đơn hàng Google Play đã hoàn tiền lại không xuất hiện trong Voided Purchases API?
API này chỉ liệt kê những đơn hàng bị hủy kèm thu hồi. Nếu một khoản hoàn tiền được xử lý mà không có tùy chọn revoke, đơn hàng đó sẽ hoàn toàn không được endpoint đó trả về, vì vậy một pipeline chỉ dựa vào nguồn dữ liệu này sẽ bỏ sót hoàn toàn những khoản hoàn tiền đó.
Tôi có thể thăm dò Voided Purchases API của Google thường xuyên đến mức nào?
Endpoint này bị giới hạn tốc độ ở mức 6.000 truy vấn mỗi ngày và 30 truy vấn trong bất kỳ khoảng thời gian 30 giây nào, vì vậy nó được thiết kế cho việc kéo dữ liệu theo lịch trình với pagination token, chứ không phải để thăm dò liên tục.

Nguồn và tài liệu đọc thêm

RefundHalt

Chế độ tự động xử lý hoàn tiền cho App Store và Google Play

Đọc tiếp

Yêu cầu hoàn tiền tiếp theo đã đang trên đường đến.

Thiết lập RefundHalt trong khoảng thời gian bạn cần để đọc thêm một email hỗ trợ về khoản hoàn tiền mà mình chưa kịp phản biện.