I have a weird issue I cannot isolate the cause of. It appears its occuring only in the UK. I'm getting sporadic reports of users who purchase my auto-renewing subscription and there is no expire date on their receipt (I'm assuming, I cannot actually see their receipt). After the purchase is made, I parse the receipt and save the expire date into
UserDefaults. Then whenever you open the app I check the expire date against today and if its later, you can access my content.I've created a GIST with my relevant code that parses the receipt and saves the expireDate that is kicked off in my appDelegate's handle purchase:
extension AppDelegate: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue,
updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchasing:
handlePurchasingState(for: transaction, in: queue)
case .purchased:
handlePurchasedState(for: transaction, in: queue)
case .restored:
handleRestoredState(for: transaction, in: queue)
case .failed:
handleFailedState(for: transaction, in: queue)
case .deferred:
handleDeferredState(for: transaction, in: queue)
}
}
}
func handlePurchasingState(for transaction: SKPaymentTransaction, in queue: SKPaymentQueue) {
print("User is attempting to purchase product id: \(transaction.payment.productIdentifier)")
}
func handlePurchasedState(for transaction: SKPaymentTransaction, in queue: SKPaymentQueue) {
print("User purchased product id: \(transaction.payment.productIdentifier)")
SubscriptionService.shared.uploadReceipt { (success, error) in
if success {
DispatchQueue.main.async {
queue.finishTransaction(transaction)
NotificationCenter.default.post(name: SubscriptionService.purchaseSuccessfulNotification, object: nil)
}
} else {
if let unwrappedError = error {
let errorDict:[String: Error] = [uploadReceiptErrorKey: unwrappedError]
NotificationCenter.default.post(name: SubscriptionService.uploadReceiptErrorNotification, object: nil, userInfo: errorDict)
} else {
NotificationCenter.default.post(name: SubscriptionService.uploadReceiptErrorNotification, object: nil)
}
}
}
}
func handleRestoredState(for transaction: SKPaymentTransaction, in queue: SKPaymentQueue) {
print("Purchase restored for product id: \(transaction.payment.productIdentifier)")
SubscriptionService.shared.uploadReceipt { (success, error) in
if success {
DispatchQueue.main.async {
queue.finishTransaction(transaction)
NotificationCenter.default.post(name: SubscriptionService.restoreSuccessfulNotification, object: nil)
}
} else {
if let unwrappedError = error {
let errorDict:[String: Error] = [uploadReceiptErrorKey: unwrappedError]
NotificationCenter.default.post(name: SubscriptionService.uploadReceiptErrorNotification, object: nil, userInfo: errorDict)
} else {
NotificationCenter.default.post(name: SubscriptionService.uploadReceiptErrorNotification, object: nil)
}
}
}
}
func handleFailedState(for transaction: SKPaymentTransaction, in queue: SKPaymentQueue) {
if let unwrappedError = transaction.error {
let errorDict:[String: Error] = [purchaseFailedErrorKey: unwrappedError]
NotificationCenter.default.post(name: SubscriptionService.purchaseFailedNotification, object: nil, userInfo: errorDict)
} else {
NotificationCenter.default.post(name: SubscriptionService.purchaseFailedNotification, object: nil)
}
queue.finishTransaction(transaction) //The RW tutorial did NOT finish the transaction, but iOS Slack said I should
print("Purchase failed for product id: \(transaction.payment.productIdentifier) on \(String(describing: transaction.transactionDate)) and this transaction is currently listed as \(transaction.transactionState) because of this error \(String(describing: transaction.error))")
}
func handleDeferredState(for transaction: SKPaymentTransaction, in queue: SKPaymentQueue) {
NotificationCenter.default.post(name: SubscriptionService.purchaseDeferredNotification, object: nil)
print("Purchase deferred for product id: \(transaction.payment.productIdentifier)")
}
}