In-App Purchases & Subscriptions

Làm thế nào để tự động phản hồi các yêu cầu hoàn tiền của Apple đối với mua hàng trong ứng dụng?

Tìm hiểu cách tự động hóa hoàn tiền của Apple có thể đơn giản hóa việc xử lý yêu cầu hoàn tiền và giảm công việc thủ công cho các nhà phát triển ứng dụng.

5 min read
Làm thế nào để tự động phản hồi các yêu cầu hoàn tiền của Apple đối với mua hàng trong ứng dụng?

Tôi mất gần cả một ngày vì lỗi Undelivered Consumption Percentage Non Zero Error. Không phải vì đây là lỗi khó. Mà vì tôi đã đặt delivery Status thành một giá trị khác DELIVERED và để consumption Percentage ở mức 60000, và Apple từ chối sự kết hợp đó mà chẳng buồn giải thích nhiều.

Đó là một cách vòng vo để dẫn vào câu hỏi thực sự.

Nếu bạn đang tìm cách tự động phản hồi các yêu cầu hoàn tiền của Apple, đây là một công việc nhỏ hơn nhiều so với những gì hầu hết các nhóm nghĩ. Phần khó không bao giờ nằm ở lệnh gọi API. Nó nằm ở việc có đủ dữ liệu để đưa vào đó. Hướng dẫn chi tiết từng trường dữ liệu được trình bày trong Apple CONSUMPTION_REQUEST explained. Bài viết này nói về cách thiết lập hệ thống để không ai phải canh thức vì nó.

Những điểm chính

  • Tự động hóa hoàn tiền Apple chỉ gồm một webhook cộng với một lệnh PUT. Apple gửi một CONSUMPTION_REQUEST, bạn phản hồi trong vòng 12 giờ.

  • Bạn cần một In-App Purchase key, không phải App Store Connect API key của bạn. Hai loại này không thể dùng thay thế cho nhau.

  • Ba trong năm trường là bắt buộc, và hai trong số đó có các ràng buộc chỉ lộ ra dưới dạng lỗi.

  • Nếu không có sự đồng ý của khách hàng, bạn không nên phản hồi, và bạn không thể thu thập sự đồng ý đó sau khi yêu cầu đã đến.

  • Endpoint đã chuyển sang v2. Môi trường production là api.storekit.apple.com, còn sandbox chỉ cho bạn 5 phút thay vì 12 giờ.

Các yêu cầu hoàn tiền của Apple dành cho nhà phát triển thực sự hoạt động như thế nào

Khách hàng mở reportaproblem.apple.com, tìm giao dịch mua trong vòng 90 ngày gần nhất, chọn một lý do, rồi gửi yêu cầu. Nếu bạn đã cấu hình App Store Server Notifications V2, endpoint của bạn sẽ nhận được một CONSUMPTION_REQUEST dưới dạng payload JWS đã ký, mang theo thông tin giao dịch, sản phẩm và lý do họ đã chọn.

Webhook đó là toàn bộ cơ chế kích hoạt cho một yêu cầu hoàn tiền mua hàng trong ứng dụng trên App Store. Không có gì hiện lên trên dashboard của bạn, và không có hàng đợi nào để kiểm tra. Đó là lý do nhiều nhóm chỉ phát hiện ra hệ thống này tồn tại sau nhiều tuần đã mất tiền vì nó.

Sau đó bạn có 12 giờ.

Có một điều cần đính chính, vì nó bị lặp lại sai ở khắp mọi nơi. Điều này không chỉ giới hạn ở sản phẩm tiêu hao (consumable). Tài liệu hiện tại của Apple cho biết CONSUMPTION_REQUEST xuất hiện cho mọi loại sản phẩm: consumable, non-consumable, non-renewing subscription, auto-renewable subscription. Các hướng dẫn cũ vẫn nói rằng chỉ áp dụng cho consumable và subscription. Những hướng dẫn đó đã lỗi thời.

"Phản hồi" có nghĩa cụ thể là gì

Bạn gửi (PUT) một JSON body đến một endpoint duy nhất.

PUT https://api.storekit.apple.com/inApps/v2/transactions/consumption/{transactionId}

Sandbox là api.storekit-sandbox.apple.com, cùng đường dẫn. Thành công trả về 202 Accepted, không phải 200, điều này khiến nhiều người bị vướng khi họ kiểm tra status code.

Việc xác thực sử dụng một ES256 JWT có thời hạn ngắn, được ký bằng một In-App Purchase key, tạo trong App Store Connect ở mục Users and Access, phần Integrations. Đây không phải là App Store Connect API key của bạn. File .p8 chỉ được tải xuống đúng một lần, ngay lúc tạo, vì vậy nếu bạn làm mất nó, bạn sẽ phải tạo một key mới và xoay vòng mọi thứ đang sử dụng nó. Cứ hỏi tôi làm sao tôi biết điều đó.

Năm trường dữ liệu, và hai trường gây rắc rối

Body là một Consumption Request. Ba trường bắt buộc, hai trường tùy chọn.

  • Customer Consented (bắt buộc). Chỉ là true khi bạn có sự đồng ý thực sự. Chi tiết hơn ở phần dưới, vì trường này quyết định toàn bộ việc có nên phản hồi hay không.

  • Delivery Status (bắt buộc). Cho biết giao dịch mua có được giao và hoạt động hay không.

  • Sample Content Provided (bắt buộc). Cho biết trước khi mua có tồn tại bản mẫu miễn phí, bản thử nghiệm, hoặc thông tin về tính năng hay không.

  • Consumption Percentage (tùy chọn). Mức độ khách hàng đã sử dụng, tính theo milliunit. Từ 0 đến 100000, do đó 45.2 phần trăm tương ứng với 45200.

  • Refund Preference (tùy chọn). GRANT_FULL, GRANT_PRORATED, DECLINE, hoặc NO_PREFERENCE.

Hai ràng buộc sau sẽ khiến bạn mất cả một buổi chiều cho mỗi cái.

Nếu Delivery Status khác DELIVERED, Consumption Percentage phải bằng 0. Bất kỳ giá trị khác sẽ thất bại với lỗi Undelivered Consumption Percentage Non Zero Error. Đó chính là ngày của tôi.

Và nếu bạn gửi GRANT_PRORATED cho một auto-renewable subscription, đừng đưa consumptionPercentage vào. Apple sẽ tự tính giá trị này dựa trên thời gian đã trôi qua trong kỳ thanh toán, và từ chối giá trị bạn gửi với lỗi Consumption Percentage Auto Renewable Subscription Error. Ít nhất tên các lỗi này cũng mô tả rõ ràng, điều mà tôi không ngờ tới.

Sự đồng ý là cánh cổng, không phải một thủ tục hình thức

Tài liệu của Apple ở đây nói rất thẳng thắn một cách khác thường: nếu khách hàng chưa đồng ý, đừng phản hồi thông báo đó. Không phải là phản hồi với Customer Consented đặt thành false. Mà là đừng phản hồi.

Bạn cũng không thể đi thu thập sự đồng ý sau khi yêu cầu đã đến. Nó phải tồn tại từ trước, nghĩa là nội dung này phải có trong điều khoản dịch vụ hoặc chính sách bảo mật của bạn trước khi bạn triển khai bất kỳ phần nào trong số này, không phải sau lần hoàn tiền tranh chấp đầu tiên của bạn. Hướng dẫn của Apple quy định rằng sự đồng ý phải được đưa ra một cách tự nguyện, cụ thể, có hiểu biết và rõ ràng, rằng opt-in tốt hơn opt-out, và việc thu thập nó hoàn toàn là trách nhiệm của bạn.

Về mặt thực tế: một câu nói rằng bạn có thể chia sẻ dữ liệu mua hàng và sử dụng với Apple để hỗ trợ xem xét các yêu cầu hoàn tiền, kèm theo một cách để rút lại sự đồng ý đó. Hãy nhờ bộ phận pháp lý của riêng bạn xem xét điều này. Tôi không phải luật sư và đây không phải là tư vấn pháp lý.

Cách nhà phát triển tự động hóa các yêu cầu hoàn tiền trên App Store

Quy trình, theo thứ tự tôi sẽ xây dựng:

  1. Cấu hình một Production Server URL trong App Store Connect, ở mục App Information, và chọn Version 2. Các consumption request không được truyền qua V1.

  2. Xác minh chuỗi ký JWS theo các chứng chỉ gốc (root certificate) của Apple trước khi tin tưởng bất kỳ trường dữ liệu nào.

  3. Khớp giao dịch với một người dùng. appAccountToken mà bạn gắn kèm lúc mua là con đường nhanh nhất. Nếu không có nó, bạn phải tra ngược theo sản phẩm và ngày, việc này nhanh chóng trở nên lộn xộn.

  4. Kiểm tra sự đồng ý. Không có đồng ý, không phản hồi. Ghi log lại và tiếp tục.

  5. Lấy dữ liệu sử dụng thực tế. Không phải con số ước tính, không phải giá trị mặc định. Nếu bạn không thể trả lời câu hỏi "họ đã sử dụng bao nhiêu phần của sản phẩm này", đó chính là lỗ hổng thực sự trong hệ thống tự động hóa của bạn.

  6. Xây dựng ConsumptionRequest, áp dụng hai ràng buộc đã nêu ở trên.

  7. Gửi nó bằng PUT, xác nhận mã 202, và lưu lại payload bạn đã gửi.

Bước 5 là nơi hầu hết các nỗ lực bị tắc lại. Lệnh gọi API chỉ là công việc của một buổi chiều. Có được dữ liệu sử dụng trung thực theo từng người dùng cho một giao dịch mua được thực hiện từ bảy tuần trước lại là một bài toán mô hình hóa dữ liệu mà bạn có thể chưa giải quyết được.

Điều gì giúp hệ thống này vận hành ổn định trong production

Ngoài kịch bản lý tưởng (happy path), có bốn điều cần lưu ý:

Idempotency (tính bất biến khi lặp). Apple sẽ gửi lại thông báo, và handler của bạn sẽ thấy cùng một thông báo hai lần. Hãy loại bỏ trùng lặp dựa trên notification UUID, nếu không bạn sẽ gửi các phản hồi trùng lặp.

Hệ thống cảnh báo thực sự gọi đến ai đó. Một handler lỗi trong âm thầm còn tệ hơn là không có handler, vì bạn sẽ tin rằng mình đã được bảo vệ.

Một bộ khung kiểm thử sandbox. Sandbox chỉ cho bạn 5 phút thay vì 12 giờ, nên bạn không thể kiểm thử việc này bằng tay một cách tùy tiện. Hãy viết script cho nó.

Và một bản ghi lại những gì bạn đã gửi, cho từng trường hợp. Khi một yêu cầu hoàn tiền bị từ chối, bạn sẽ muốn biết mình đã gửi preference nào và tại sao. Khi một yêu cầu được chấp thuận dù bạn đã gửi DECLINE, bạn cũng sẽ muốn biết điều đó.

Ước tính thực tế: vài ngày để ghép nối các phần nếu bạn đã từng xây dựng hệ thống webhook trước đây, và vài tuần để có thể tin tưởng vào nó. Phần lớn thời gian đó không nằm ở phía Apple.

Hoặc để thứ khác làm việc đó

Nếu bạn đang dùng RevenueCat hoặc Adapty, hãy kiểm tra những gì họ đã cung cấp trước. Cả hai đều đã bổ sung tính năng phản hồi hoàn tiền, và biên lai cùng dữ liệu khách hàng của bạn đã có sẵn ở đó.

Phần mềm quản lý hoàn tiền Apple được xây dựng chuyên biệt còn đi xa hơn. Refund Sensor hoạt động trên các App Store Connect key bạn đã có sẵn, phản hồi mọi yêu cầu bằng bằng chứng giao dịch trong thời hạn cho phép, và ghi log kết quả cho từng trường hợp. Không cần SDK, không cần thay đổi code, chỉ khoảng năm phút để kết nối. Chúng tôi đã so sánh các lựa chọn khác trên thị trường trong Apple refund management tools, nếu bạn muốn tham khảo thêm.

Góc nhìn từ khách hàng, vì có người hỏi

Về cách yêu cầu hoàn tiền cho các giao dịch mua trên App Store, từ góc độ người dùng: reportaproblem.apple.com, đăng nhập, tìm giao dịch mua trong vòng 90 ngày gần nhất, chọn một lý do. Apple thông báo cho khách hàng rằng họ sẽ nhận được phản hồi trong vòng 48 giờ.

Bạn cũng có thể hiển thị cùng biểu mẫu đó ngay trong ứng dụng của mình bằng cách sử dụng refund request sheet của StoreKit, giúp dẫn người dùng không hài lòng đến Apple thay vì đến một đánh giá một sao. Nó không thể cấp hoàn tiền. Chỉ Apple mới có thể làm điều đó. Apple Support's guide to requesting a refund trình bày những gì khách hàng sẽ nhìn thấy.

Cần nói rõ điều này, vì việc quản lý hoàn tiền trên App Store thường bị thổi phồng: bạn không thể phê duyệt hay từ chối một yêu cầu hoàn tiền. Bạn chỉ có thể đưa bằng chứng ra trước Apple trong khi họ đang ra quyết định, và nêu ra một preference. Đó là toàn bộ mức độ ảnh hưởng bạn có, và nó nhiều hơn những gì hầu hết nhà phát triển sử dụng.

Nguồn tham khảo

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

Có. Đó chính là mục đích tồn tại của thông báo CONSUMPTION_REQUEST và endpoint Send Consumption Information. Bạn không thể cấp hoặc từ chối hoàn tiền, nhưng Apple sử dụng những gì bạn gửi làm một yếu tố đầu vào cho quyết định của họ.

Nhận App Store Server Notifications V2, xác minh chữ ký JWS, khớp giao dịch với một người dùng, kiểm tra sự đồng ý, tổng hợp dữ liệu sử dụng thực tế, và gửi (PUT) nó đến endpoint consumption v2 trong vòng 12 giờ. Mọi thứ còn lại là idempotency, cảnh báo và lưu trữ hồ sơ.

Một In-App Purchase key từ mục Users and Access, phần Integrations. App Store Connect API key của bạn sẽ không xác thực được các lệnh gọi này, và bạn sẽ nhận được lỗi 401 trông giống như vấn đề về đồng hồ hệ thống.

Không, đây là trường tùy chọn. Nhưng nếu deliveryStatus khác DELIVERED thì giá trị này phải bằng 0, và đối với một auto-renewable subscription được tính theo tỷ lệ (prorated), bạn phải bỏ hẳn trường này.

Đừng phản hồi. Hướng dẫn của Apple nói rất rõ về điều này, và sự đồng ý không thể được thu thập sau khi yêu cầu đã đến.

Đó là một yếu tố đầu vào, không phải một sự đảm bảo. Apple nói rằng họ sử dụng nhiều yếu tố khác nhau. Trong thực tế, một DECLINE có bằng chứng rõ ràng cho một giao dịch mua đã được sử dụng nhiều sẽ bị từ chối thường xuyên hơn nhiều so với một yêu cầu không ai phản hồi

#:Google Play chargeback#Google Play chargeback handling#Chargeback automation#Google Play refunds#App revenue protection#Chargeback management for apps
Refund SensorRefund Sensor TeamRefund defense for App Store and Google Play developers