Swift3 - Get transaction date when restoring In-App Purchase Transactions - ios

i am trying to restore in-app purchase (non-renewing subscription) in case a user installs the app on a new phone or re-installs it on his phone. I want to get the original purchase date for an in-app purchase using the method
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
for transaction in queue.transactions{
let t : SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
print("transaction finished",prodID)
if(prodID == "com.netvariant.FamousFaces.FullAccess"){
print(">>>>>>>>>>>>>", t.original?.transactionDate)
print(">>>>>>>>>>>>>", t.transactionDate)
defaults.set("Y", forKey: "adPurchase")
NotificationCenter.default.post(name: Notification.Name(rawValue: "removeAds"), object: nil)
print("working")
}
}
}
The problem is transaction date in returning nil in both cases. What did i miss in the code ? and is this the best way to handle restored purchases?
I want to get the date to calculate if the users's subscription period (1 month) is over or not. The restore is successful but as i mentioned the date in nil.
Thank you.

You can use SwiftyStoreKit for verify and Perform InApp Purchase. Below code for verify InApp purchase is expired or not. For User this code, your SwiftyStoreKit in your project.
let appleValidator = AppleReceiptValidator(service: .production)
SwiftyStoreKit.verifyReceipt(using: appleValidator, password: "Enter your inapp secret key") { result in
switch result {
case .success(let receipt):
// Verify the purchase of a Subscription
let purchaseResult = SwiftyStoreKit.verifySubscription(
type: .autoRenewable, // or .nonRenewing (see below)
productId: "Enter your inapp product key",
inReceipt: receipt)
switch purchaseResult {
case .purchased(let expiryDate, _):
print("Product is valid until \(expiryDate)")
case .expired(let expiryDate, _):
print("Product is expired since \(expiryDate)")
case .notPurchased:
print("The user has never purchased this product")
}
case .error(let error):
print("Receipt verification failed: \(error)")
}
}

Related

Should i have to verify apple in app purchase old receipts on fail or cancel,

sample code here of the cancel function, should I verify receipt or all scenarios are covered without doing this.
private func fail(transaction: SKPaymentTransaction) {
print("fail...")
print(transaction.error?.localizedDescription ?? "");
SKPaymentQueue.default().finishTransaction(transaction)
if let recieptString = self?.getRecieptString(){
self?.uploadRecieptToServer(reciept: recieptString, isFailier: true)
}
}

iOS in-app purchases Automatically purchase after restore completed

I'm testing in-app purchases with sandbox. In paymentQueue updateTransactions method I catch to transaction states then if state purchase or restore I try to unlock content(user has been subscribed then turn mode to Premium).
so far everything is works perfectly. The user would be purchase item, then user goes to Premium mode. If user removed application from his device, restore case also works and turn mode to premium.
But somehow, After user clicked to restore button in that time no issue occurs. But if user close to app then open twice user would see a purchased cases again. Some purchase cases works again without any purchase attempt.
I understand is that after adding SKProductQueue observer updatedTransaction method calling. Then somehow purchase case is works.
It's very hard to describe what I'm getting into but basically after user clicked to restore button next time updatedTransaction method work suddenly and purchased case works.Also I will put buy and restore functions but these are correct it seems like.
Any idea would be grateful!
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
print(transaction.transactionState.status(), transaction.payment.productIdentifier)
switch transaction.transactionState {
case .purchasing : break
case .purchased:
// set true for premium mode then turn back to main screen from purchase screen.
UserDefaults.standard.setValue(true, forKey: "isUserSubscribe")
UserDefaults.standard.synchronize()
getBackMainScreenProvider.Instance.returnMain()
print("purchased")
queue.finishTransaction(transaction)
break
case .failed:
queue.finishTransaction(transaction)
break
case .restored:
// this case is works more than once but if user has more than purchase
// but its okay.
UserDefaults.standard.setValue(true, forKey: "isUserSubscribe")
UserDefaults.standard.synchronize()
getBackMainScreenProvider.Instance.returnMain()
AppDelegate.restoreClickFlag = true
queue.finishTransaction(transaction)
break
default : break
}
}
}
func purchase (product: IAPProduct) {
guard let productToPurchase = products.filter({ $0.productIdentifier == product.rawValue }).first else { return }
let payment = SKPayment(product: productToPurchase) // request to the Appstore in order to payment process
paymentQueue.add(payment)
print("nasıl...")
}
func restorePurchases () {
print("restoring purchases...")
paymentQueue.restoreCompletedTransactions()
}

Swift inapp purchase fails but system dialog say you're all set

I have an app which has inApp purchase feature. The problem is even if I get error, such as the fail case because of "Cannot connect to iTunes Store", the system dialog says "You are all set. Your purchase was successful". You can find my inApp purchase helper class code down.
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
NotificationCenter.default.post(name: .IAPHelperSetPepqueenNotification, object: nil)
if let url = Bundle.main.appStoreReceiptURL {
guard let receipt = try? Data(contentsOf: url) else {
print("error to take receipt")
return
}
let receiptData: String = receipt.base64EncodedString(options: .init(rawValue: 0))
PepappNetwork.request(target: .postReceipt(platform: "ios", receipt: receiptData) , success: { (JSON) in
print(JSON)
let user = User(JSON: JSON["data"].dictionaryObject!)
UserDefaults.standard.set(user?.identifier, forKey: "userID")
user?.persist()
if user?.language != nil {
UserDefaults.standard.set(user!.language!, forKey: "forcedLanguage")
UserDefaults(suiteName: Constants.UserDefaults.containerName)!.set(user!.language!, forKey: "forcedLanguage")
}
NotificationCenter.default.post(name: Notification.Name.CurrentUserChanged, object: nil)
self.complete(transaction: transaction)
}, error: { (errorString, _) in
}) { (MoyaError) in
}
}
break
case .failed:
NotificationCenter.default.post(name: .IAPHelperCancelNotification, object: nil)
fail(transaction: transaction)
break
case .restored:
restore(transaction: transaction)
break
case .deferred:
break
case .purchasing:
break
}
}
}
Fail transaction func
private func fail(transaction: SKPaymentTransaction) {
print("fail...")
if let transactionError = transaction.error as NSError?,
let localizedDescription = transaction.error?.localizedDescription,
transactionError.code != SKError.paymentCancelled.rawValue {
print("Transaction Error: \(localizedDescription)")
}
Before app enter update transactions function "You're all set." dialog has already been show.
There was an issue with the Apple Sandbox, It has been resolved now - https://developer.apple.com/system-status/
Seems like it's a recent Apple bug. I started experiencing it today and it appears when making purchase with sandbox account.
However, in-app purchases still work if you upload an app to TestFlight.

How to check purchase status of In-App Purchase (Auto Renewing Subscription)?

I created an In-App Purchase feature for the app, using an Auto-Renewing Subscription. The problem is that I'm not sure how to check the purchase status, whether the subscription has run out or is still active.
Here's where I try saving the purchased status:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("add payment")
for transaction: AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
print(trans.error)
switch trans.transactionState {
case .purchased:
print("buy ok, unlock IAP HERE")
print(p.productIdentifier)
let prodID = p.productIdentifier
switch prodID {
case "com.test.UnlockTools.Subscription1":
print("tool set 1 unlocked")
uTool1()
print("tool set 2 unlocked")
uTool2()
print("tool set 3 unlocked")
uTool3()
UserDefaults.standard.set(true, forKey: "isSubbed")
default:
print("IAP not found")
}
queue.finishTransaction(trans)
case .failed:
print("buy error")
queue.finishTransaction(trans)
break
default:
print("Default")
break
}
}
}
This is where I call the UserDefaults and allow or deny button interaction:
override func viewDidLoad() {
super.viewDidLoad()
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID: NSSet = NSSet(object: "com.test.UnlockTools.Subscription1")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
status = UserDefaults.standard.bool(forKey: "isSubbed") ?? false
if status == true {
unlockTool1.isUserInteractionEnabled = true
unlockTool2.isUserInteractionEnabled = true
unlockTool3.isUserInteractionEnabled = true
} else {
unlockTool1.isUserInteractionEnabled = false
unlockTool2.isUserInteractionEnabled = false
unlockTool3.isUserInteractionEnabled = false
}
}
If I find out the status then I will be able to save the state as true/false. I tried using UserDefaults with some success. Not sure if I placed the code in the best location though.
If the subscription is still active, I'd like to allow the user to click a button, and deny the button click if the subscription has run out.
I'll add more code if needed. Any help would be greatly appreciated.
The only way to check the subscription status is to verify the receipt with Apple to see if it's still valid using their /verifyReceipt endpoint - docs.
What you could do is cache some expiration date after the purchase and use that to check if the subscription is valid. If it's passed the expiration date you can re-check the receipt with Apple to see if it's renewed. There are also edge cases where a user is refunded and their subscription is cancelled before the expiration date - you should update your receipts periodically with Apple to check this case. Ideally, this should all be happening server side as well to avoid piracy.
Here's a great post that summarizes the nauces of Apple subscriptions very well: iOS Subscriptions are Hard

IAPs are being restoring when they have not yet been purchased yet

When the user calls restorePurchases(), the non-consumable com.premium is restored even thought they do not own it.
Here are the functions that are responsible for the restoring purchases and purchasing IAPs. This is only an issue with non-consumable IAPs.
There is no issue with purchasing. And if the user attempts to purchase an IAP that they already have, it is simply restored. Thank you for looking in to this.
func restorePurchases() {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
print("transactions restored")
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
print("starting restoring")
switch prodID {
case "com.premium":
print("restoring ads")
removeAds()
case "com.cash":
print("we dont restore coins")
case "com.level":
print("we dont restore levels")
default:
print("can't restore")
}
}
Here is my payment queue also.
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("add paymnet")
for transaction:AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
print(trans.error)
switch trans.transactionState {
case .purchased:
print("buying, ok unlock iap here")
print(p.productIdentifier)
let prodID = p.productIdentifier as String
switch prodID {
case "com.premium":
print("buying ads")
removeAds()
case "com.cash":
print("buying coins")
addCoins()
case "com.level":
print("buying levels")
addLevels()
default:
print("can't buy")
}
queue.finishTransaction(trans)
break;
case .failed:
print("buy error")
queue.finishTransaction(trans)
break;
default:
print("default")
break;
}
}
}
You should not update any purchase status in paymentQueueRestoreCompletedTransactionsFinished. This function just lets you know that the restoration process has completed. You could use this to update your UI or display an alert or something.
The restoration process delivers the transactions to be restored to the updatedTransactions function where you handle the .restored state in the same way that you handle the .purchased state.
Essentially "restore" just replays the purchase transaction process for non-consumable and auto-renewing subscription purchase types.

Resources