StoreKit not responding - ios

I was following the Kilo Loco tutorial for store kit but I can't seem to purchase the product when its is called. This is my class:
import Foundation
import StoreKit
enum GameProducts: String {
case removeAds = "BundleID" // I have registered one but Id rather keep it private
}
class GameIAP: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
static let shared = GameIAP()
var products = [SKProduct]()
let myQueue = SKPaymentQueue.default()
override init() {
}
func getProduct() {
let products: Set = [GameProducts.removeAds.rawValue]
let request = SKProductsRequest(productIdentifiers: products)
request.delegate = self
request.start()
myQueue.add(self)
}
func purchase(product: GameProducts) {
guard let theProduct = products.filter({ $0.productIdentifier == product.rawValue}).first else
{ return }
let payment = SKPayment(product: theProduct)
myQueue.add(payment)
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
products = response.products
}
func restore() {
myQueue.restoreCompletedTransactions()
}
func complete(transaction: SKPaymentTransaction) {
SKPaymentQueue.default().finishTransaction(transaction)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "payment"), object: nil)
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
print(transaction.transactionState.currentStatus(), transaction.payment.productIdentifier)
switch transaction.transactionState {
case .purchased: complete(transaction: transaction)
default: break
}
}
}
}
extension SKPaymentTransactionState {
func currentStatus() -> String {
switch self {
case .deferred: return "Deferred"
case .failed: return "Failed"
case .purchased: return "Purchased"
case .purchasing: return "Purchasing"
case .restored: return "Restored"
}
}
}
When the ViewController loads it calls getProduct() and then when I hit a button it calls the purchase function but this doesn't seem to work??
The restore function works absolutely fine but the purchase doesn't return anything...
Has anyone done this before and know where I've gone wrong or is this something on app store connect???

Related

In App Purchase - detect whether user has already made that purchase

I have the following code to make In-App-Purchases. Everything works fine and the user is able to purchase (non-consumable) on button click.
Currently I call getProducts() and restorePurchases() inside of AppDelegate - when the user has already made a purchase, I assume it will be fetched with restorePurchases() - how can I detect whether a specific product with its ID has already been purchased to f.e. hide that button.
import Foundation
import StoreKit
class IAPService: NSObject {
private override init() {}
static let shared = IAPService()
var products = [SKProduct]()
let paymentQueue = SKPaymentQueue.default()
private var purchasedProductIdentifiers: Set<String> = []
func getProducts() {
let product: Set = ["123456"]
let request = SKProductsRequest(productIdentifiers: product)
request.delegate = self
request.start()
paymentQueue.add(self)
}
func purchase() {
guard let productToPurchase = products.first else { return }
print(productToPurchase)
let payment = SKPayment(product: productToPurchase)
paymentQueue.add(payment)
}
func restorePurchases() {
paymentQueue.restoreCompletedTransactions()
}
}
extension IAPService: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
self.products = response.products
for product in response.products {
print(product.localizedTitle)
}
}
}
extension IAPService: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
print(transaction.transactionState.status(), transaction.payment.productIdentifier)
switch transaction.transactionState {
case .purchasing: break
default: queue.finishTransaction(transaction)
}
}
}
}
extension SKPaymentTransactionState {
func status() -> String {
switch self {
case .deferred: return "deferred"
case .failed: return "failed"
case .purchased: return "purchased"
case .purchasing: return "purchasing"
case .restored: return "restored"
}
}
}
You need to check callback of updatedTransactions for the needed productIdentifier and it's state purchased/restored
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
if transaction.payment.productIdentifier == "someId" && ( transaction.transactionState == .purchased || transaction.transactionState == .restored ) {
// purchased / resorted
}
}
}
Useful links
https://www.raywenderlich.com/5456-in-app-purchase-tutorial-getting-started
in-app purchase in Swift with a single product

IAP autoRenewable subscriptions, SKPaymentQueue update transaction function is not responding

I have implemented in app purchases for auto renewable subscriptions in my app, but I am trying to implement something once a purchased has been completed.
Below is my SubscriptionService for the IAPs
class SubscriptionService: NSObject {
static let sessionIdSetNotification = Notification.Name("SubscriptionServiceSessionIdSetNotification")
static let optionsLoadedNotification = Notification.Name("SubscriptionServiceOptionsLoadedNotification")
static let restoreSuccessfulNotification = Notification.Name("SubscriptionServiceRestoreSuccessfulNotification")
static let purchaseSuccessfulNotification = Notification.Name("SubscriptionServiceRestoreSuccessfulNotification")
static let shared = SubscriptionService()
private var purchasedProductIdentifiers: Set<ProductIdentifier> = []
var productDidPurchased: (() -> Void)?
var hasReceiptData: Bool {
return loadReceipt() != nil
}
var options: [Subscription]? {
didSet {
NotificationCenter.default.post(name: SubscriptionService.optionsLoadedNotification, object: options)
}
}
func loadSubscriptionOptions() {
let products: Set = ["productIDS"]
let request = SKProductsRequest(productIdentifiers: products)
request.delegate = self
request.start()
}
func uploadReceipt(completion: ((_ success: Bool) -> Void)? = nil) {
if let receiptData = loadReceipt() {
}
}
private func loadReceipt() -> Data? {
guard let url = Bundle.main.appStoreReceiptURL else {
return nil
}
do {
let data = try Data(contentsOf: url)
return data
} catch {
print("Error loading receipt data: \(error.localizedDescription)")
return nil
}
}
func purchase(subscription: Subscription) {
let payment = SKPayment(product: subscription.product)
print("Product being bought: \(payment)")
SKPaymentQueue.default().add(payment)
}
func restorePurchases() {
SKPaymentQueue.default().restoreCompletedTransactions()
}
}
extension SubscriptionService: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
options = response.products.map { Subscription(product: $0) }
print("in here 1")
print(options!)
// let purchased = UserDefaults.standard.bool(forKey: "com.mylawnow.sub.allaccess")
//print("Purchased\(purchased)")
print("in here 2")
}
func request(_ request: SKRequest, didFailWithError error: Error) {
if request is SKProductsRequest {
print("Subscription Options Failed Loading: \(error.localizedDescription)")
}
}
}
//-------------------------------------------------------------
This is the part that does not seem to be working. None of the functions seem to be firing (I have implemented print statements to see if they are ever hit, but they don't seem to be.)
extension SubscriptionService: SKPaymentTransactionObserver{
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("updating")
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
print("purchased made")
complete(transaction: transaction)
break
case .failed:
print("purchased failed")
fail(transaction: transaction)
break
case .restored:
print("restored")
restore(transaction: transaction)
break
case .deferred:
print("purchase deferred")
break
case .purchasing:
print("purchase being made")
break
}
}
}
I am assuming that the complete function should be used to do certain functionality once the purchased is made.
private func complete(transaction: SKPaymentTransaction) {
print("completed...")
deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func restore(transaction: SKPaymentTransaction) {
guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
deliverPurchaseNotificationFor(identifier: productIdentifier)
print("identifier: \(productIdentifier)")
SKPaymentQueue.default().finishTransaction(transaction)
}
private func fail(transaction: SKPaymentTransaction) {
print("failed...")
if let transactionError = transaction.error as NSError?,
let localizedDescription = transaction.error?.localizedDescription,
transactionError.code != SKError.paymentCancelled.rawValue {
print("Transaction Error: \(localizedDescription)")
}
SKPaymentQueue.default().finishTransaction(transaction)
}
private func deliverPurchaseNotificationFor(identifier: String?) {
guard let identifier = identifier else { return }
purchasedProductIdentifiers.insert(identifier)
UserDefaults.standard.set(true, forKey: identifier)
NotificationCenter.default.post(name: .IAPHelperPurchaseNotification, object: identifier)
}
}
So I found the error. In my loadSubscriptions function, I needed to app an observer to the SKPaymentQueue. So it should be like below:
func loadSubscriptionOptions() {
let products: Set = ["productIDS"]
let request = SKProductsRequest(productIdentifiers: products)
request.delegate = self
request.start()
SKPaymentQueue.default().add(self) //--> this line is needed
}

In App for Two Non Consumable products in Sprite Kit

I am making a game in Sprite Kit.
I have IAPProducts Swift File with code:
import Foundation
enum IAPProducts: String {
case nonConsumable1 = "Kingdom.Lion"
case nonConsumable2 = "Kingdom.Sheep"
}
and IAPService.Swift file with code:
import Foundation
import StoreKit
var lionpurchased = false
var sheeppurchased = false
class IAPService: NSObject {
private override init() {}
static let shared = IAPService()
var products = [SKProduct]()
let paymentQueue = SKPaymentQueue.default()
func getProducts() {
let products: Set = [
IAPProducts.nonConsumable1.rawValue,
IAPProducts.nonConsumable2.rawValue
]
let request = SKProductsRequest(productIdentifiers: products)
request.delegate = self
request.start()
paymentQueue.add(self)
}
func purchase(product: IAPProducts) {
guard let productToPurchase = products.filter({ $0.productIdentifier == product.rawValue }).first else { return }
let payment = SKPayment(product: productToPurchase)
paymentQueue.add(payment)
}
func restorePurchases() {
print("restore purchases")
paymentQueue.restoreCompletedTransactions()
}
}
extension IAPService: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
products = response.products
for product in response.products {
print(product.localizedTitle)
}
}
}
extension IAPService: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
print(transaction.transactionState.status(), transaction.payment.productIdentifier)
switch transaction.transactionState {
case .purchasing: break
default: queue.finishTransaction(transaction)
}
}
}
}
extension SKPaymentTransactionState {
func status() -> String {
switch self {
case .deferred: return "deferred"
case .failed: return "failed"
case .purchased:
return "purchased"
case .purchasing: return "purchasing"
case .restored: return "restored"
}
}
}
The problem is I need to know which NonConsumable Product was purchased? I need to know what should I check is equal to IAPProducts.nonConsumable1.rawValue and IAPProducts.nonConsumable2.rawValue
In your paymentQueue(_:updatedTransactions:) method you need to check the state of each transaction. If the state is .purchased, then look at the transaction's payment. The payment's productIdentifier tells you which in-app purchase was purchased.
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
if transaction.state == .purchased {
let productId = transaction.payment.productIdentifier
// Your code to handle the completed purchase of this IAP
}
queue.finishTransaction(transaction)
}
}

How to handle shouldAddStorePayment for In-App Purchases in iOS 11?

I'm trying to implement that new paymentQueue(_:shouldAddStorePayment:for:) method so my app can handle IAPs directly from the App Store.
I'm using the itms-services:// url to test it, like it says here.
The thing is, my SKPaymentTransactionObserver is a specific view controller, and if it's not visible when I open the itms-services:// link the delegate method won't be called.
What can I do about that? I think I'd have to detect if the user is coming from the App Store to push the right view controller, but I don't know how. The only other option I can think of right now is to make the App Delegate an SKPaymentTransactionObserver, but that seems really cumbersome and I couldn't get it to work when I tried it. Is there any other way?
Here's a class I did that can help you achieve what you want, simply copy the code below and paste it inside a new file and then you can simply access the class StoreManager.shared to whatever method/variable you want to access.
1- To init this class, just call from your didFinishLaunchingWithOptions
StoreManager.shared.Begin() and then payment observer is added.
import Foundation
import StoreKit
class StoreManager: NSObject{
/**
Initialize StoreManager and load subscriptions SKProducts from Store
*/
static let shared = StoreManager()
func Begin() {
print("StoreManager initialized"))
}
override init() {
super.init()
// Add pyament observer to payment qu
SKPaymentQueue.default().add(self)
}
func requestProductWithID(identifers:Set<String>){
if SKPaymentQueue.canMakePayments() {
let request = SKProductsRequest(productIdentifiers:
identifers)
request.delegate = self
request.start()
} else {
print("ERROR: Store Not Available")
}
}
func buyProduct(product: SKProduct) {
print("Buying \(product.productIdentifier)...")
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
func restorePurchases() {
SKPaymentQueue.default().restoreCompletedTransactions()
}
}
// MARK:
// MARK: SKProductsRequestDelegate
//The delegate receives the product information that the request was interested in.
extension StoreManager:SKProductsRequestDelegate{
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
var products = response.products as [SKProduct]
var buys = [SKProduct]()
if (products.count > 0) {
for i in 0 ..< products.count {
let product = products[i]
print("Product Found: ",product.localizedTitle)
}
} else {
print("No products found")
}
let productsInvalidIds = response.invalidProductIdentifiers
for product in productsInvalidIds {
print("Product not found: \(product)")
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("Something went wrong: \(error.localizedDescription)")
}
}
// MARK:
// MARK: SKTransactions
extension StoreManager: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
completeTransaction(transaction: transaction)
break
case .failed:
failedTransaction(transaction: transaction)
break
case .restored:
restoreTransaction(transaction: transaction)
break
case .deferred:
// TODO show user that is waiting for approval
break
case .purchasing:
break
}
}
}
private func completeTransaction(transaction: SKPaymentTransaction) {
print("completeTransaction...")
deliverPurchaseForIdentifier(identifier: transaction.payment.productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func restoreTransaction(transaction: SKPaymentTransaction) {
guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
print("restoreTransaction... \(productIdentifier)")
deliverPurchaseForIdentifier(identifier: productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func failedTransaction(transaction: SKPaymentTransaction) {
if let error = transaction.error as NSError? {
if error.domain == SKErrorDomain {
// handle all possible errors
switch (error.code) {
case SKError.unknown.rawValue:
print("Unknown error")
case SKError.clientInvalid.rawValue:
print("client is not allowed to issue the request")
case SKError.paymentCancelled.rawValue:
print("user cancelled the request")
case SKError.paymentInvalid.rawValue:
print("purchase identifier was invalid")
case SKError.paymentNotAllowed.rawValue:
print("this device is not allowed to make the payment")
default:
break;
}
}
}
SKPaymentQueue.default().finishTransaction(transaction)
}
private func deliverPurchaseForIdentifier(identifier: String?) {
guard let identifier = identifier else { return }
}
}
//In-App Purchases App Store
extension StoreManager{
func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
return true
//To hold
//return false
//And then to continue
//SKPaymentQueue.default().add(savedPayment)
}
}

In-App Purchase consumable being restored automatically

I'm doing my first In-App purchase and a weird behaviour is that an consumable product is being restored instead of creating new transaction.
I've followed the tuto https://www.raywenderlich.com/122144/in-app-purchase-tutorial
Which I found an elegant approach.
here is my StoreKit helper:
import StoreKit
public typealias ProductIdentifier = String
public typealias ProductsRequestCompletionHandler = (_ success: Bool, _ products: [SKProduct]?) -> ()
open class IAPHelper : NSObject {
fileprivate let productIdentifiers: Set<ProductIdentifier>
fileprivate var purchasedProductIdentifiers: Set<ProductIdentifier> = Set()
fileprivate var productsRequest: SKProductsRequest?
fileprivate var productsRequestCompletionHandler: ProductsRequestCompletionHandler?
static let IAPHelperPurchaseNotification = "IAPHelperPurchaseNotification"
public init(productIds: Set<ProductIdentifier>) {
productIdentifiers = productIds
super.init()
SKPaymentQueue.default().add(self)
}
}
// MARK: - StoreKit API
extension IAPHelper {
public func requestProducts(_ completionHandler: #escaping ProductsRequestCompletionHandler) {
productsRequest?.cancel()
productsRequestCompletionHandler = completionHandler
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
productsRequest!.delegate = self
productsRequest!.start()
}
public func buyProduct(_ product: SKProduct) {
print("Buying \(product.productIdentifier)...")
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
public func isProductPurchased(_ productIdentifier: ProductIdentifier) -> Bool {
return purchasedProductIdentifiers.contains(productIdentifier)
}
public class func canMakePayments() -> Bool {
return SKPaymentQueue.canMakePayments()
}
public func restorePurchases() {
SKPaymentQueue.default().restoreCompletedTransactions()
}
}
// MARK: - SKProductsRequestDelegate
extension IAPHelper: SKProductsRequestDelegate {
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("Loaded list of products...")
let products = response.products
productsRequestCompletionHandler?(true, products)
clearRequestAndHandler()
for p in products {
print("Found product: \(p.productIdentifier) \(p.localizedTitle) \(p.price.floatValue)")
}
}
public func request(_ request: SKRequest, didFailWithError error: Error) {
print("Failed to load list of products.")
print("Error: \(error.localizedDescription)")
productsRequestCompletionHandler?(false, nil)
clearRequestAndHandler()
}
private func clearRequestAndHandler() {
productsRequest = nil
productsRequestCompletionHandler = nil
}
}
// MARK: - SKPaymentTransactionObserver
extension IAPHelper: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
complete(transaction: transaction)
break
case .failed:
fail(transaction: transaction)
break
case .restored:
restore(transaction: transaction)
break
case .deferred:
break
case .purchasing:
break
}
}
}
private func complete(transaction: SKPaymentTransaction) {
print("complete...")
validateReceipt()
deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func restore(transaction: SKPaymentTransaction) {
guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
print("restore... \(productIdentifier)")
deliverPurchaseNotificationFor(identifier: productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func fail(transaction: SKPaymentTransaction) {
print("fail...")
if let transactionError = transaction.error as? NSError {
if transactionError.code != SKError.paymentCancelled.rawValue {
print("Transaction Error: \(transaction.error?.localizedDescription)")
}
}
SKPaymentQueue.default().finishTransaction(transaction)
}
private func deliverPurchaseNotificationFor(identifier: String?) {
guard let identifier = identifier else { return }
purchasedProductIdentifiers.insert(identifier)
UserDefaults.standard.set(true, forKey: identifier)
UserDefaults.standard.synchronize()
NotificationCenter.default.post(name: NSNotification.Name(rawValue: IAPHelper.IAPHelperPurchaseNotification), object: identifier)
}
}
I'm able to buy once, second it is restored automatically:
This In-App purchase has already been bought. It will be restored for
free
When I get this message, none of IAPHelper methods is called.
My iTunes showing it is a consumable:
Even uninstalling the app the purchased still being restored.
It really looks like an Apple bug as my firsts tests I could buy 2..3 times without this message.
If it is not a bug, how can I prevent this situation ?
For those stuck in this silly situation, here are some WA and solution:
queue.finishTransaction(transaction)
IMPORTANT: Finish the transaction in your paymentQueue method:
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
complete(transaction: transaction)
queue.finishTransaction(transaction)
break
case .failed:
fail(transaction: transaction)
queue.finishTransaction(transaction)
break
case .restored:
restore(transaction: transaction)
queue.finishTransaction(transaction)
break
case .deferred:
break
case .purchasing:
break
}
}
}
This will avoid the situation.
If you are already stuck on it, here is the work around, just for tests purposes !
for transaction in SKPaymentQueue.default().transactions {
print(transaction)
SKPaymentQueue.default().finishTransaction(transaction)
}
You can place it on a temporary button and clear all transactions while testing in one tap.
At least it saved my life today.

Resources