I have no trouble purchasing items one at a time, but when I add two items to the queue in quick succession with SKPaymentQueue.default().add(), the second transaction is never called by updatedTransactions(). Debugging the queue contents shows that the second transaction is in the queue, with a transactionState of .purchasing.
Relevant Code:
// AppDelegate.swift
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let inAppPurchasesHelper = InAppPurchasesHelper()
inAppPurchasesHelper.setAsTransactionObserver()
}
}
// InAppPurchasesHelper.swift
class InAppPurchasesHelper : NSObject, SKPaymentTransactionObserver {
func setAsTransactionObserver() {
SKPaymentQueue.default().add(self)
}
func buy(productIdentifier: String) {
let product = // product retrieval from app data model using productIdentifier
SKPaymentQueue.default().add(SKPayment(product: product))
print("Added \(item.productIdentifier) to SKPaymentQueue.")
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case SKPaymentTransactionState.purchased:
print("Purchased \(transaction.payment.productIdentifier)")
downloadTransaction(transaction)
break
case SKPaymentTransactionState.failed:
print("Failed \(transaction.payment.productIdentifier)")
failedTransaction(transaction)
break
case SKPaymentTransactionState.restored:
print("Restored \(transaction.payment.productIdentifier)")
downloadTransaction(transaction)
break
case SKPaymentTransactionState.deferred:
print("Deferred \(transaction.payment.productIdentifier)")
break
case SKPaymentTransactionState.purchasing:
print("Purchasing \(transaction.payment.productIdentifier)")
break
}
}
}
func downloadTransaction(_ transaction: SKPaymentTransaction) {
if let downloads = transaction.downloads as [SKDownload]? {
SKPaymentQueue.default().start(downloads)
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) {
for download in downloads {
switch (download.state) {
case .waiting:
break
case .active:
break
case .finished:
finishedDownload(download)
break
case .failed:
failedDownload(download)
break
case .cancelled:
failedDownload(download)
case .paused:
break
}
}
}
}
Related
i am using Alamofire, SwiftyJSON to get data, but i can not understand how do i use error alert in ViewConroller, so my code here..
class Networking {
static func FetchData() {
AF.request("https://ApiApiApiApi", method: .get).validate().responseJSON { responseJSON -> Void in
switch responseJSON.result {
case .success:
print("Validation Successful")
case .failure(let error):
// Here i need to show alert in viewController
print("\(error)")
}
}
}
}
}
If i got error i need to show alert in the ViewContoller
class ViewContoller: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Networking.FetchData()
}
}
Firstly you can create an enum for error messages.
enum YourErrorMessage: String, Error {
case failed = "There is an error"
}
And then add a completion handler to your method.
class Networking {
static func FetchData(completion: #escaping(YourErrorMessage) -> Void) {
AF.request("https://ApiApiApiApi", method: .get).validate().responseJSON { responseJSON -> Void in
switch responseJSON.result {
case .success:
print("Validation Successful")
case .failure(let error):
completion(.failed)
print("\(error)")
}
}
}
}
Now you can reach the method state(failure, success).
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Networking.FetchData { errorMessage in
print(errorMessage.rawValue)
}
}
}
I am trying IAP for the first time and I need some help connecting the dots.
My app has two different non-consumable IAPs which I have set up in a "product" class if its own like this:
enum IAPProduct: String{
case tempoLevels = ".....TempoLevels"
case timingLevels = ".....TimingLevels"
}
Then I set up a helper class via a tutorial I found like this:
class IAPService: NSObject {
private override init() {}
static let shared = IAPService()
var products = [SKProduct]()
let paymentQueue = SKPaymentQueue.default()
func getProducts() {
let products: Set = [IAPProduct.tempoLevels.rawValue, IAPProduct.timingLevels.rawValue]
let request = SKProductsRequest(productIdentifiers: products)
request.delegate = self
request.start()
paymentQueue.add(self)
}
func purchase(product: IAPProduct) {
guard let productToPurchase = products.filter({ $0.productIdentifier == product.rawValue }).first
else {return}
let payment = SKPayment(product: productToPurchase)
paymentQueue.add(payment)
}
}
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"
#unknown default:
return("Something is wrong...")
}
}
}
I would like to trigger some functions in another view controller when the product is purchased. How do I do this?
I sort of set up a function in one of the view controllers that checks UserDefaults in one of the view controllers like this:
func isPurchased() -> Bool {
let purchaseStatus = UserDefaults.standard.bool(forKey: ??)
if purchaseStatus == true {
print("Previously purchased!")
return true
}else{
print("Never purchased!")
return false
}
}
I'm not sure how I can use this function, but if I can somehow, I have code in my app that would work with it if at all possible.
You can use directly SKPaymentTransactionState for purchase checking.
You can call like thi after creating the transaction if want to Bool value
func isPurchased(transaction: SKPaymentTransaction) -> Bool {
return transaction.transactionState == .purchased
}
or directly String value from extension ofSKPaymentTransactionState
func isPurchased(transaction: SKPaymentTransaction) -> String {
return transaction.status
}
In the mean time, you should not ever store a boolean for checking if user has bought in-app purchase in UserDefaults. User can change it very easily (without jailbreaking) and get your goodies for free! You should Use Keychain instead of UserDefaults.
I have two async function which send requests to my server.
DispatchQueue.global(qos: .userInitiated).async {
weak var weakself = self
self.unregisterPushServer(token: token!) { [weak self] success in
print("0")
if success {
print("1")
weakself?.unregisterPushInfoKeychain(token: token!)
print("2")
if let this = self {
print("PLEASE")
weakself?.registerPushServer(token: token!) { [weak this] success in
print("3")
if success {
print("4")
this?.registerPushInfoKeychain()
print("5")
}
}
}
print("success")
}
}
}
And the functions are
private func registerPushServer(token: String, completion: #escaping (Bool) -> ()) {
request() { (data, error) in
if data != nil {
completion(true)
} else {
completion(false)
}
}
private func unregisterPushServer(token: String, completion: #escaping (Bool) -> ()) {
request2() { (data, error) in
if data != nil {
completion(true)
} else {
completion(false)
}
}
But in console,
0
1
2
success
not seemed to executes codes after my PLEASE sign.
Why is my code is not working?
I first thought that the problem was about the queue, but it was not.
You don't need this line:
weak var weakself = self
By including [weak self] in the closure's capture list, self automatically becomes weak.
Try and replace the instances of weakself with just self.
I'm also thinking you may not even need the if let this = self condition.
I hope this helps.
OK, the problem was not in this code.
When I call this function, I did it like this.
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
let pushService = PushService()
pushService.updateRegistrationStatus(token: fcmToken)
}
the
pushService.updateRegistrationStatus(token: fcmToken)
was the function which contains the code I asked above.
In this situation, the function updateRegistrationStatus is not guaranteed because pushService itself is released by ARC when messaging(...) function block is end.
class AppDelegate: UIResponder, UIApplicationDelegate {
let pushService = PushService()
...
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
self.pushService.updateRegistrationStatus(token: fcmToken)
}
}
Now the pushService object is not released because it is declared as a global variable.
I am writing my first iOS app in Swift and have been having some problems with the in-app purchases functionality. I think I've got the actual buy function working but I'm not sure about the restore functionality. I'm just providing one product which is a 'pro' version of the app. This is stored in the User Defaults location and having it unlocks the extra functionality. My restore function is confusing me, can someone have a look at it and tell me where I should be setting my User Defaults value? Should it be within the paymentQueueRestoreCompletedTransactionsFinished function or the paymentQueue function?
import UIKit
import StoreKit
class StoreController: UIViewController, SKProductsRequestDelegate,
SKPaymentTransactionObserver {
// Properties
var defaults = UserDefaults.standard
var list = [SKProduct]()
var p = SKProduct()
// Methods
override func viewDidLoad() {
super.viewDidLoad()
// Get list of products for the store
localTitle.isEnabled = false
localDescription.isEnabled = false
price.isEnabled = false
buy.isEnabled = false
restore.isEnabled = false
getProducts()
}
// Outlets
#IBOutlet weak var localTitle: UILabel!
#IBOutlet weak var localDescription: UILabel!
#IBOutlet weak var price: UILabel!
#IBOutlet weak var buy: UIButton!
#IBOutlet weak var restore: UIButton!
// Actions
#IBAction func buy(_ sender: UIButton) {
print("buy pressed")
for product in list {
let prodID = product.productIdentifier
if(prodID == "com.squidgylabs.pro") {
p = product
buyProduct()
}
}
}
#IBAction func restore(_ sender: UIButton) {
print("restore pressed")
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
// Methods
func getProducts() {
print("starting getproducts function")
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID: NSSet = NSSet(objects: "com.squidgylabs.pro")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("starting productsrequest")
let myProduct = response.products
for product in myProduct {
print("product added")
list.append(product)
}
localTitle.isEnabled = true
localDescription.isEnabled = true
restore.isEnabled = true
buy.isEnabled = true
price.isEnabled = true
// Update labels
localTitle.text = list[0].localizedTitle
localDescription.text = list[0].localizedDescription
// Format the price and display
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .currency
if let formattedPrice = formatter.string(from: list[0].price){
price.text = formattedPrice
}
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
print("starting paymentQueueResoreCompletedTransactionsFnished")
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "com.squidgylabs.pro":
print("case is correct product ID in payment...finished")
default:
print("IAP not found")
}
}
}
func buyProduct() {
print("buy " + p.productIdentifier)
let pay = SKPayment(product: p)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(pay as SKPayment)
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("entering func paymentQueue")
for transaction: AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
switch trans.transactionState {
case .purchased:
print("buy ok, unlock IAP HERE")
print(p.productIdentifier)
let prodID = p.productIdentifier
switch prodID {
case "com.squidgylabs.pro":
print("setting pro in defaults...")
defaults.set(true, forKey: "pro")
default:
print("IAP not found")
}
queue.finishTransaction(trans)
break
case .failed:
print("buy error")
queue.finishTransaction(trans)
break
case .restored:
print("case .restored in paymentQ")
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
queue.finishTransaction(trans)
break
default:
print("Default")
break
}
}
}
}
It may be done like this.
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
completePurchaseForTransaction(transaction)
case .restored:
completePurchaseForTransaction(transaction)
default:
break
}
}
private func completePurchaseForTransaction(_ transaction: SKPaymentTransaction) {
let productIdentifier = transaction.payment.productIdentifier
// Update UserDefaults here
SKPaymentQueue.default().finishTransaction(transaction)
}
You might consider using other class for StoreKit access instead of a UIViewController. Because
Your application should always expect to be notified of completed
transactions.
https://developer.apple.com/documentation/storekit/skpaymentqueue/1506042-add
And View controllers come and go. It might even crash the app if you don't remove object you added as an observer to the SKPaymentQueue.
I have simple, well-documented StoreKit library on GitHub that you might check out to get the idea -- https://github.com/suvov/VSStoreKit
I am trying to implement in app purchases in a Swift app, of a consumable in-app purchase, with product ID productID
Research
Using this answer, I learned about the basic pattern for in app purchases, and tried to adapt it, like so. I do not need to use the NSUserDefaults method of having the app remember if a purchase is made, as I am handling that with a different solution.
Code
class ViewController: UICollectionViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
viewDidLoad() {
//Unimportant stuff left out
product_id = "productID"
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
}
#IBAction func thisISTheButtonToTriggerTheInAppPurchases(sender: AnyObject) {
if (SKPaymentQueue.canMakePayments()) {
var productID:NSSet = NSSet(object: self.product_id!);
var productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>);
productsRequest.delegate = self;
productsRequest.start();
print("Fetching Products");
}else{
print("can't make purchases");
}
}
func buyProduct(product: SKProduct){
print("Sending the Payment Request to Apple");
var payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment);
}
func productsRequest (request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
var count : Int = response.products.count
if (count>0) {
var validProducts = response.products
var validProduct: SKProduct = response.products[0] as SKProduct
if (validProduct.productIdentifier == self.product_id) {
print(validProduct.localizedTitle)
print(validProduct.localizedDescription)
print(validProduct.price)
buyProduct(validProduct);
} else {
print(validProduct.productIdentifier)
}
} else {
print("nothing")
}
}
func request(request: SKRequest!, didFailWithError error: NSError!) {
print("Error Fetching product information");
}
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
print("Received Payment Transaction Response from Apple");
for transaction:AnyObject in transactions {
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction{
switch trans.transactionState {
case .Purchased:
print("Product Purchased");
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
break;
case .Failed:
print("Purchased Failed");
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
break;
case .Restored:
print("Already Purchased");
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
default:
break;
}
}
}
}
}
Error
Type 'ViewController' does not conform to protocol 'SKPaymentTransactionObserver'
I researched this error a bit, and sources like this suggest that such an error is due to not having required functions.
According to Apple, paymentQueue:updatedTransactions: is the only one required, but I seem to be implementing that. A possible theory of mine is that I am not implementing it correctly - if true, how do I fix that?
Expected Behavior
The app presents, and if given favorable user input, performs the IAP with the ID productID.
Thanks for helping, and let me know if you need any more information!
It's very simple. The error is about SKPaymentTransactionObserver. Its only required method is:
func paymentQueue(queue: SKPaymentQueue,
updatedTransactions transactions: [SKPaymentTransaction])
The signature for your method doesn't match that:
func paymentQueue(queue: SKPaymentQueue!,
updatedTransactions transactions: [AnyObject]!)
...so it isn't the same method, so you don't conform.