I created a monthly subscription (auto-renewing) and installed a trial of 14 days for it in the App Store Connect. How to activate it now? I want that immediately after registering the user in the application a window appears with a proposal to purchase a monthly subscription or take advantage of the 14-day free version. How to use it in code?
It doesn't matter whether or not your subscription has introductory period. You need to implement:
1. products fetching using SKProductsRequest:
func loadProducts(){
let request = SKProductsRequest.init(productIdentifiers: productIds)
request.delegate = self
request.start()
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
products = response.products
}
initiate payment process of that product using SKPaymentQueue:
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
Handle each switch-case in updated transactions method:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
SKPaymentQueue.default().finishTransaction(transaction)
// handle here
break
case .failed:
SKPaymentQueue.default().finishTransaction(transaction)
// handle here
break
case .restored:
SKPaymentQueue.default().finishTransaction(transaction)
// handle here
break
case .deferred, .purchasing:
break
default:
break
}
}
More details you can read in my article: https://blog.apphud.com/swift-tutorial-subscriptions/
You can also use the SwiftyStoreKit library.
Related
I am following the tutorial for IAP from the following site:
https://betterprogramming.pub/set-up-your-swiftui-app-to-support-in-app-purchases-ef2e0a11d10c
Here is the portion of the code that handles the transaction and the states:
extension IAPManager: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
transactions.forEach { (transaction) in
switch transaction.transactionState {
case .purchased:
SKPaymentQueue.default().finishTransaction(transaction)
purchasePublisher.send(("Purchased ",true))
case .restored:
totalRestoredPurchases += 1
SKPaymentQueue.default().finishTransaction(transaction)
purchasePublisher.send(("Restored ",true))
case .failed:
if let error = transaction.error as? SKError {
purchasePublisher.send(("Payment Error \(error.code) ",false))
print("Payment Failed \(error.code)")
}
SKPaymentQueue.default().finishTransaction(transaction)
case .deferred:
print("Ask Mom ...")
purchasePublisher.send(("Payment Diferred ",false))
case .purchasing:
print("working on it...")
purchasePublisher.send(("Payment in Process ",false))
default:
break
}
}
}
}
In short, it checks each case on the queue for each buy request you placed with the server and sends the status back to the SwiftUI interface through the purchasePublisher PassThruSubject that looks like this:
let purchasePublisher = PassthroughSubject<(String, Bool), Never>()
That is the part I am confused about!? How do I access the purchasePublisher so that I can check on the SwiftUI view (SwiftUI interface) that the purchase was in fact completed successfully so that then I can take action accordingly?
purchasePublisher is an instance of PassthroughSubject, which is a publisher that broadcasts information to subscribers. You must go through this instance to access those subscriptions and you can do that with the sink method:
purchasePublisher.sink(receiveValue: { (value) in
print(value)
})
And there are, as always, caveats. You may find something like this useful for what you're doing: .send() and .sink() do not seem to work anymore for PassthroughSubject in Xcode 11 Beta 5
I'm getting sporadic reports of users who purchase my auto-renewing subscription and there is no receipt data on their device. 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)")
queue.finishTransaction(transaction)
SubscriptionService.shared.uploadReceipt { (success) in
DispatchQueue.main.async {
//self.updateWatchContext()
NotificationCenter.default.post(name: SubscriptionService.purchaseSuccessfulNotification, object: nil)
}
}
}
func handleRestoredState(for transaction: SKPaymentTransaction, in queue: SKPaymentQueue) {
print("Purchase restored for product id: \(transaction.payment.productIdentifier)")
queue.finishTransaction(transaction)
SubscriptionService.shared.uploadReceipt { (success) in
DispatchQueue.main.async {
NotificationCenter.default.post(name: SubscriptionService.restoreSuccessfulNotification, object: nil)
}
}
}
func handleFailedState(for transaction: SKPaymentTransaction, in queue: SKPaymentQueue) {
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) {
print("Purchase deferred for product id: \(transaction.payment.productIdentifier)")
}
}
A few users have told me that they cannot unlock the content even though they subscribed (these users also said they had purchased the annual subscription, I also have a monthly option)...I had one user install a beta version with a Firebase integration so that I could push the receipt and expire date to the console to see what it says and it appears that there is no expire date or receipt on his device. Any thoughts on what the issue is and why this seems to only be the case in the UK? My active user base is roughly 600 and I haven't heard of this issue outside of the UK.
I have a problem with my code. The function updatedTransactions is only called once while the transaction is .Purchasing and is not called after the transaction has ben completed.
func buyProduct(product: SKProduct) {
let payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
print(transaction)
switch (transaction.transactionState) {
case .Purchased, .Restored:
print("Completed")
complete(transaction)
break
case .Failed:
fail(transaction)
break
default:
break
}
}
}
Sorry I might answer a bit late, but it might be related to this question. Are you testing your IAP from Xcode when it doesn't reach the .purchased state?
If yes, then you need to set your device's App Store ID to one you have created on iTunes Connect (User menu, Sandbox testers).
If no, then maybe it could be because your SKPaymentQueue already contains too many transactions waiting to be finished. You can check it with SKPaymentQueue.default().transactions.
In my case I had 28 transactions waiting to be finished because I had a bad switch case statement during the purchase phase.
If this is the case, you can add those lines in your viewDidLoad to finish each one of them (don't forget to remove them after you find why they didn't finish in your code):
for transaction: AnyObject in SKPaymentQueue.default().transactions {
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
}
I can buy and restore non-consummable products without any trouble.
But if I try to restore from an account that never bought the product, the method productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) is never called whereas it is called on a successful restore.
Here is my code in the class that conforms to the SKPaymentTransactionObserver protocol:
public func restorePurchases() {
print("restoring...")
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
// finishtTransaction() is called within the sub functions
switch (transaction.transactionState) {
case .purchased:
print("pruchased !")
complete(transaction: transaction)
break
case .failed:
print("failed !")
fail(transaction: transaction)
break
case .restored:
print("restored !")
restore(transaction: transaction)
break
case .deferred:
print("deferred !")
break
case .purchasing:
print("purchasing !")
break
default:
print("default...")
break
}
}
}
Please note that I can buy and restore products, I just never receive an answer when there is nothing to restore.
In fact, you just need to implement the following method:
public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
if (queue.transactions.count == 0)
{
print("Nothing to restore...")
}
}
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.