iOS内购:如何通过收据数据获取已取消订阅的活跃产品?
Hey there! Let's walk through exactly how to pull the active product from a canceled subscription using your app's receipt data. Here's a step-by-step breakdown with code examples to make it concrete:
1. Fetch and Parse the Receipt
First, you'll need to retrieve and parse your app's receipt (either the local receipt or the validated one from Apple's server). Convert the receipt data into a dictionary you can work with:
guard let receiptURL = Bundle.main.appStoreReceiptURL, let receiptData = try? Data(contentsOf: receiptURL), let receiptDict = try? JSONSerialization.jsonObject(with: receiptData, options: []) as? [String: Any] else { // Handle receipt not found or parsing error return nil }
2. Access Subscription Transaction Records
The receipt's latest_receipt_info array contains all subscription transaction entries (each entry represents a billing cycle). You'll want to filter this array to focus on the subscription in question, using the original_transaction_id (this ID stays consistent across all billing cycles for the same subscription):
guard let latestReceiptInfo = receiptDict["latest_receipt_info"] as? [[String: Any]], let targetOriginalTransactionId = "YOUR_TARGET_ORIGINAL_TRANSACTION_ID" else { return nil } // Filter entries for the specific subscription let subscriptionEntries = latestReceiptInfo.filter { entry in entry["original_transaction_id"] as? String == targetOriginalTransactionId }
3. Identify Canceled Subscriptions with Active Access
A canceled subscription will have a cancel_date field in its transaction entry. But remember: even if canceled, the subscription remains active until its expires_date passes. We need to check if the expiration date is still in the future:
// Helper to convert ISO 8601 receipt dates to Date func receiptDate(from dateString: String?) -> Date? { guard let dateString = dateString else { return nil } let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] return formatter.date(from: dateString) } let now = Date() // Find the latest active canceled subscription entry if let activeCanceledEntry = subscriptionEntries.last(where: { entry in // Check if the subscription was canceled entry["cancel_date"] != nil && // Check if it's still active (expiration date is after now) let expiresDate = receiptDate(from: entry["expires_date"] as? String), expiresDate > now }) { // Extract the active product ID let activeProductId = activeCanceledEntry["product_id"] as? String print("Currently active product for canceled subscription: \(activeProductId ?? "Unknown")") return activeProductId } else { // No active canceled subscription found return nil }
Key Notes to Remember
original_transaction_idis critical: This links all billing cycles for the same subscription, so use it to group entries correctly.- Cancel vs. Expired: A canceled subscription isn't immediately inactive—users keep access until their current paid cycle ends. Always validate the
expires_dateagainst the current time. - Server-Side Validation: For production, it's highly recommended to validate receipts on your backend (instead of locally) to avoid tampering. The same logic applies—parse the server's response for the
latest_receipt_infoarray and apply the checks above.
内容的提问来源于stack exchange,提问作者yagnik suthar

