Phát hiện hoàn tiền trong StoreKit 2 quy về một property duy nhất trên transaction, và đó là revocationDate
Khi Apple hoàn tiền cho một khách hàng của bạn, khoản hoàn tiền đó đã nằm sẵn bên trong app của bạn trên revocationDate của transaction, trước khi tác vụ máy chủ của bạn chạy. Đây là nơi phát hiện hoàn tiền StoreKit 2 hiện ra trên thiết bị, revocationDate và revocationReason cho bạn biết điều gì, và tại sao client là để nhanh còn server là để đúng.

Điểm chính
- Trong StoreKit 2 một purchase đã được hoàn tiền mang một revocationDate non-nil trên Transaction của nó, nên app của bạn có thể tự phát hiện khoản hoàn tiền, mà không cần gọi máy chủ của bạn.
- revocationDate được đặt khi App Store hoàn tiền một transaction hoặc khi một khách hàng mất nó qua Family Sharing, nên một ngày non-nil không phải lúc nào cũng là hoàn tiền.
- revocationReason cho bạn biết lý do: developerIssue nghĩa là khách hàng nêu ra một vấn đề trong app của bạn, và other bao gồm mọi lý do hoàn tiền còn lại.
- Transaction.currentEntitlements vốn đã loại trừ các purchase đã hoàn tiền và bị thu hồi, nên cổng kiểm soát truy cập phía client sạch nhất chỉ đơn giản là liệu một product có còn xuất hiện ở đó hay không.
- Transaction.updates chỉ chuyển giao một khoản hoàn tiền xảy ra khi app của bạn đang đóng nếu bạn bắt đầu lắng nghe lúc khởi chạy, nên một Task bị thiếu nghĩa là một khoản hoàn tiền bị bỏ lỡ.
- Phát hiện phía client chỉ kích hoạt khi app đang mở, đó là lý do REFUND của App Store Server Notifications V2 vẫn là tín hiệu có thẩm quyền ngăn bạn trả tiền để phục vụ một người dùng đã được hoàn tiền.
- Một khoản hoàn tiền có thể bị đảo ngược, và khi điều đó xảy ra, các trường revocation bị gỡ khỏi transaction và bạn được kỳ vọng khôi phục lại quyền truy cập mà bạn đã cắt.
Hầu hết app biết về một khoản hoàn tiền của Apple từ máy chủ của họ, qua một App Store Server Notification, và không bao giờ nhận ra rằng cùng khoản hoàn tiền đó đã nằm sẵn bên trong app. Nó nằm trên transaction, trong một property tên là revocationDate, và đọc nó cho phép app của bạn cắt quyền truy cập của một khách hàng đã được hoàn tiền vào lần tiếp theo họ mở app, thay vì chờ một tác vụ backend. Phát hiện hoàn tiền StoreKit 2 là một tín hiệu phía client mà hầu hết đội ngũ bỏ qua. Đây là chính xác nơi một khoản hoàn tiền hiện ra trên thiết bị, nó cho bạn biết điều gì, không cho biết điều gì, và tại sao nó thuộc về bên cạnh các thông báo máy chủ của bạn chứ không phải thay thế chúng.
Nơi một khoản hoàn tiền hiện ra bên trong StoreKit 2
StoreKit 2 trao cho bạn các transaction dưới dạng giá trị đã ký, và một khoản hoàn tiền không xóa transaction. Nó đánh dấu transaction. Hai property trên Transaction mang dấu đó, và cả hai đều ở nil suốt vòng đời của một purchase khỏe mạnh. Khi một trong hai trở thành non-nil, App Store đã lấy lại purchase đó.
revocationDate là trường lật giá trị
revocationDate là một Date tùy chọn. Mô tả của chính Apple rất chính xác: đó là ngày App Store hoàn tiền transaction hoặc thu hồi nó khỏi Family Sharing. Với một purchase vẫn còn tốt, nó là nil. Ngay khi một khoản hoàn tiền được xử lý, nó giữ dấu thời gian của khoản hoàn tiền đó. Chỉ một kiểm tra đó, liệu revocationDate có non-nil không, đó là toàn bộ việc phát hiện hoàn tiền phía client. Mọi thứ khác chỉ là các sắc thái chồng lên trên.
revocationReason cho bạn biết tại sao Apple thu hồi nó
revocationReason nằm ngay cạnh ngày và giải thích nguyên nhân. StoreKit cho nó hai giá trị quan trọng đối với hoàn tiền. developerIssue nghĩa là khách hàng nói với Apple rằng khoản hoàn tiền là do một vấn đề thực tế hoặc được cảm nhận trong app của bạn. other bao gồm mọi lý do còn lại. Một giá trị thứ ba, upgradedToBundle, hoàn toàn không phải hoàn tiền; nó đánh dấu một transaction mà App Store thu hồi vì khách hàng chuyển sang một subscription bundle. Hãy đọc lý do trước khi bạn hành động, vì developerIssue là cái đáng đếm: một cụm chúng chính là sản phẩm của bạn đang cho bạn biết nó hỏng ở đâu.
| Property | Type | Một giá trị non-nil nghĩa là gì |
|---|---|---|
revocationDate | Date? | App Store đã hoàn tiền transaction này, hoặc thu hồi nó qua Family Sharing, vào ngày này |
revocationReason là developerIssue | reason | Khách hàng nêu một vấn đề thực tế hoặc được cảm nhận trong app của bạn |
revocationReason là other | reason | Khoản hoàn tiền xảy ra vì một lý do khác mà Apple không liệt kê chi tiết |
revocationReason là upgradedToBundle | reason | Không phải hoàn tiền; transaction bị thu hồi vì khách hàng chuyển sang một subscription bundle |
currentEntitlements vốn đã loại bỏ một purchase đã hoàn tiền
Bạn không phải lúc nào cũng tự đọc các trường revocation. Transaction.currentEntitlements là chuỗi các purchase mà khách hàng vẫn còn quyền ngay lúc này, và Apple dựng nó để loại ra những cái bạn không nên tôn trọng. Một product mà App Store đã hoàn tiền hoặc thu hồi không xuất hiện trong đó. Các subscription hết hạn cũng vậy, hay các consumable, vốn biến mất ngay khi chúng được dùng xong.
Điều đó làm cho currentEntitlements trở thành cổng sạch nhất để kiểm soát truy cập. Hãy hỏi nó khách hàng sở hữu gì, cấp đúng thứ đó, và một khoản hoàn tiền gỡ bỏ entitlement giúp bạn mà không cần một kiểm tra revocationDate nào. Các trường revocation là dành cho khi bạn muốn chi tiết, ngày và lý do, để ghi lại sự kiện hoặc phản ứng với nó. Danh sách entitlement là dành cho câu hỏi đơn giản là có nên tiếp tục duy trì hay không.
Phát hiện hoàn tiền StoreKit 2 trong thực tế, từ lúc khởi chạy và khi app đang chạy
Có hai thời điểm app của bạn có thể bắt được một khoản hoàn tiền trên thiết bị, và chúng cần mã khác nhau. Một là khi app đang mở và một khoản hoàn tiền xảy ra trực tiếp hoặc trên một thiết bị khác. Cái kia là lúc khởi chạy, bắt kịp mọi thứ đã thay đổi khi bạn đang đóng. Bỏ lỡ cái thứ hai và việc phát hiện hoàn tiền StoreKit 2 của bạn có một lỗ hổng đúng ngay nơi hầu hết các khoản hoàn tiền rơi vào, vì khách hàng hiếm khi mở app của bạn khi họ yêu cầu hoàn tiền.
Bắt đầu lắng nghe lúc khởi chạy nếu không bạn bỏ lỡ các khoản hoàn tiền xảy ra khi đang đóng
Transaction.updates là chuỗi async phát ra một transaction bất cứ khi nào hệ thống tạo hoặc cập nhật một transaction bên ngoài app của bạn hoặc trên một thiết bị khác, bao gồm một khoản hoàn tiền. Chỉ dẫn của Apple thẳng thừng: hãy khởi động một Task lặp qua nó ngay khi app của bạn khởi chạy, nếu không bạn có thể bỏ lỡ các transaction mà nó chỉ chuyển giao một lần lúc khởi động. Một khoản hoàn tiền đến qua đêm sẽ đến qua updates vào lần tiếp theo app mở, nhưng chỉ khi một listener đã chạy sẵn để nhận nó. Không listener, không event, và khoản hoàn tiền vẫn vô hình cho đến khi một thứ khác đối soát nó.
Một purchase trên cùng thiết bị không đến qua updates
Một cái bẫy tóm được những người thử hoàn tiền bằng tay. Một purchase thông thường thực hiện trên cùng thiết bị không đến qua updates; StoreKit trả nó thẳng từ kết quả của lệnh gọi purchase. updates là dành cho các thay đổi ngoài luồng: các khoản hoàn tiền, các phê duyệt Ask to Buy, các lần đổi offer-code, và các purchase thực hiện ở nơi khác. Vậy nên hãy xây dựng việc xử lý hoàn tiền của bạn quanh updates và currentEntitlements, chứ không phải quanh luồng purchase, vì khoản hoàn tiền sẽ không bao giờ đi ngược lại qua con đường mà việc bán hàng đã đi.

Điều mà phát hiện phía client không thể làm cho bạn
Đọc các khoản hoàn tiền trên thiết bị thì nhanh và miễn phí, nhưng nó có một trần, và giả vờ là không có chính là cách doanh thu rò rỉ. Thiết bị chỉ biết những gì StoreKit đã nói với nó, và StoreKit chỉ lên tiếng khi app của bạn đang chạy. Một khách hàng được hoàn tiền và không bao giờ mở lại app của bạn là một khách hàng mà kiểm tra phía client của bạn không bao giờ thấy.
Một revocationDate không phải lúc nào cũng là hoàn tiền
Cùng trường đó lật giá trị vì Family Sharing. Khi một khách hàng mất quyền truy cập vào một purchase được chia sẻ, vì người tổ chức gỡ họ ra hoặc việc chia sẻ kết thúc, transaction đó cũng nhận một revocationDate. Vậy nên một ngày non-nil nghĩa là khách hàng không còn purchase này, đúng là điều bạn cần cho việc kiểm soát truy cập, nhưng không phải lúc nào cũng nghĩa là tiền đã trở ra. Nếu bạn đang đếm các khoản hoàn tiền cho doanh thu, hãy tách các lần thu hồi Family Sharing khỏi các khoản thật sự trước khi bạn tin vào con số.
Một khoản hoàn tiền có thể bị đảo ngược
Một khoản hoàn tiền không phải lúc nào cũng là cuối cùng. Apple có thể đảo ngược một khoản, và khi làm vậy, các trường revocation bị gỡ khỏi transaction và purchase lại hợp lệ. Nếu bạn cắt quyền truy cập khi hoàn tiền, bạn được kỳ vọng khôi phục nó khi có sự đảo ngược. Trên thiết bị điều đó hiện ra như một event updates khác với một transaction sạch; trên máy chủ của bạn đó là một thông báo REFUND_REVERSED riêng biệt. Chỉ xử lý khoản hoàn tiền và bạn sẽ bỏ mặc một khách hàng đang trả tiền không có quyền truy cập mà lại có một hóa đơn còn hiệu lực.
Một lần thu hồi muộn thật sự tốn kém đến đâu
Một khoản hoàn tiền hiếm khi chỉ là giá bán rời khỏi tài khoản của bạn. Đến lúc nó được xử lý xong bạn thường đã tiêu tiền thật để phục vụ purchase đó rồi, và khoản chi đó không trở lại. Các hình ảnh được tạo ra tốn phút GPU. Các câu trả lời chat tốn các lệnh gọi model API mà bạn bị tính phí theo từng token. Các lần tải lên tốn dung lượng lưu trữ mà bạn vẫn đang trả tiền để giữ. Nếu purchase tài trợ một khoản chi trả cho một nhà sáng tạo, số tiền đó đã ra khỏi cửa. Không thứ nào trong số đó đảo ngược cùng với khoản hoàn tiền.
Phát hiện phía client thu hẹp cửa sổ trên một phần duy nhất bạn vẫn có thể kiểm soát, đó là chi tiêu tương lai. Bạn càng sớm biết một purchase đã được hoàn tiền, bạn càng sớm ngừng phục vụ nó. Nhưng thiết bị chỉ cho bạn biết khi app đang mở, nên một người dùng đã được hoàn tiền mà không bao giờ quay lại vẫn giữ bất kỳ quyền truy cập phía máy chủ nào bạn đã cấp, lặng lẽ khiến bạn tốn kém mỗi lần một tác vụ nền hoặc một thiết bị đã đồng bộ hành động thay cho họ. Client làm cho việc thu hồi nhanh. Nó không làm cho việc đó chắc chắn.
Dùng client để nhanh và server để đúng
Thiết kế sạch dùng cả hai tín hiệu cho việc mà mỗi cái giỏi. Trên thiết bị, Transaction.updates và currentEntitlements cho bạn một phản ứng cục bộ tức thì ngay khi một khách hàng đã được hoàn tiền mở app, tốt cho UI và cho việc hoàn tất các thay đổi entitlement mà không cần một chuyến đi khứ hồi. Trên máy chủ, App Store Server Notifications V2 gửi một thông báo REFUND đến bất kể app có bao giờ được mở lại hay không, đó là tín hiệu duy nhất ngăn backend của bạn một cách đáng tin cậy khỏi chi tiêu trên một tài khoản đã được hoàn tiền.
| Signal | Nó tồn tại ở đâu | Kích hoạt khi | Hãy tin nó để |
|---|---|---|---|
revocationDate trên một transaction | Device, StoreKit 2 | App của bạn đọc transaction | Cho bạn biết một purchase cụ thể đã được hoàn tiền hoặc thu hồi |
Transaction.updates | Device, StoreKit 2 | Một khoản hoàn tiền đến khi app đang chạy, hoặc lúc khởi chạy nếu bạn lắng nghe | Phản ứng tức thì cho một khách hàng đang hiện diện |
currentEntitlements | Device, StoreKit 2 | Bạn kiểm tra khách hàng sở hữu gì lúc này | Kiểm soát truy cập mà không cần tự theo dõi các khoản hoàn tiền |
REFUND notification | Máy chủ của bạn, App Store Server Notifications V2 | Apple xử lý khoản hoàn tiền, dù app mở hay không | Ngừng chi tiêu phía máy chủ trên một khách hàng không bao giờ quay lại |
Hãy đấu nối các tín hiệu thiết bị cho khách hàng đang cầm điện thoại, và thông báo máy chủ cho người không cầm. Khoản hoàn tiền hiện ra ở cả hai nơi một cách có chủ đích. Chỉ đọc một trong hai chính là cách một tài khoản đã được hoàn tiền tiếp tục khiến bạn tốn kém sau khi việc bán hàng đã mất từ lâu.
Câu hỏi thường gặp
- Làm thế nào để tôi phát hiện một khoản hoàn tiền trong StoreKit 2?
- Hãy kiểm tra revocationDate của transaction. Nó là nil đối với một purchase hợp lệ và giữ một ngày một khi App Store hoàn tiền transaction, nên một revocationDate non-nil là tín hiệu rằng một purchase đã được hoàn tiền hoặc thu hồi.
- Sự khác biệt giữa revocationDate và revocationReason là gì?
- revocationDate là khi App Store lấy lại purchase, và revocationReason là lý do tại sao. Lý do là developerIssue khi khách hàng nêu một vấn đề trong app của bạn và other cho bất cứ điều gì khác.
- Một purchase đã được hoàn tiền có còn xuất hiện trong currentEntitlements không?
- Không. Transaction.currentEntitlements loại trừ các purchase mà App Store đã hoàn tiền hoặc thu hồi, nên một product đã hoàn tiền tự rơi khỏi entitlements của khách hàng, điều này làm cho nó thành một cổng an toàn để kiểm soát truy cập.
- StoreKit có báo cho app của tôi về một khoản hoàn tiền xảy ra khi app đang đóng không?
- Chỉ khi bạn lắng nghe từ lúc khởi chạy. Transaction.updates chuyển giao những thay đổi đó một lần lúc khởi động, nên bạn phải khởi động một Task lặp qua nó ngay khi app của bạn khởi chạy nếu không khoản hoàn tiền bị bỏ lỡ cho đến khi một thứ khác đối soát nó.
- Phát hiện hoàn tiền phía client có đủ khi đứng một mình không?
- Không. Thiết bị chỉ biết về một khoản hoàn tiền khi app của bạn đang chạy, nên một khách hàng không bao giờ mở lại app là vô hình với nó. REFUND của App Store Server Notifications V2 là tín hiệu đến với bạn bất kể điều đó.
- Một revocationDate có luôn nghĩa là khách hàng đã được hoàn tiền không?
- Không. revocationDate cũng được đặt khi một khách hàng mất một purchase qua Family Sharing, nên một ngày non-nil nghĩa là họ không còn purchase đó nhưng không phải lúc nào cũng nghĩa là tiền đã được trả lại.
Nguồn và tài liệu đọc thêm
- Apple Developer: Transaction.revocationDate (StoreKit)
- Apple Developer: Transaction.RevocationReason (StoreKit)
- Apple Developer: Transaction.currentEntitlements (StoreKit)
- Apple Developer: Transaction.updates (StoreKit)
- Apple Developer: App Store Server Notifications V2 notificationType (REFUND, REFUND_REVERSED)
- Apple Developer Tech Talk: Support customers with StoreKit 2 and App Store Server API
RefundHalt
Chế độ tự động xử lý hoàn tiền cho App Store và Google Play
Đọc tiếp
Mỗi cửa sổ hoàn tiền của App Store và Google Play là một cuộc đếm ngược, và đây là số giờ mà mỗi cửa sổ cho bạn
Mỗi khoản hoàn tiền trên App Store và Google Play đều khởi động một chiếc đồng hồ, và phần lớn chúng chạy mà không cần bạn. Cửa sổ hoàn tiền ngắn nhất của Apple là 12 giờ, cửa sổ chargeback của Google là 24 giờ, và từ ngày 3 tháng 8 năm 2026, một cửa sổ chargeback bị bỏ lỡ là một hóa đơn, không chỉ là một đơn hàng mất đi. Đây là mọi thời hạn chạm đến tài khoản của bạn.
Thông báo giao dịch bị hủy cho phép máy chủ Google Play của bạn thu hồi quyền truy cập ngay khi một khoản hoàn tiền xảy ra
Google Play có thể đẩy tới máy chủ của bạn một thông báo giao dịch bị hủy ngay lập tức khi một giao dịch được hoàn tiền, bị bồi hoàn, hoặc bị hủy. Nó mang theo purchaseToken, orderId, productType và refundType, và nó có nghĩa một điều, thu hồi quyền truy cập. Đây là cách đọc nó và kết nối nó.