I have implemented an IAP Scene OptionsPage: SKScene where users can purchase "Remove Ads" and in game currency etc. But if I go from MenuScene: SKScene to the OptionsPage: SKScene and back to the menu quickly it crashes with Thread 1: EXC_BAD_ACCESS (code=1, address=0x216d0716) and the only thing in the log down the bottom is (lldb).
I am calling SKPaymentQueue.defaultQueue().removeTransactionObserver(self) in willMoveFromView().
In my didMoveToView I call the setIAP() function (shown below).
After constantly bashing my head against a table (first time dealing with IAP) , I think that it is happening because I've set request.delegate = self and so when I enter the scene request.start() runs but because I quickly leave the scene request.delegate = self is no longer valid.
I thought removing the transaction observer would deal with this in willMoveFromView. How can I get around this problem?
Here is how I am calling the purchase in touchesBegan()
if removeAds.containsPoint(location) {
for product in list {
let prodID = product.productIdentifier
if (prodID == "removeAds") {
p = product
buyProduct()
break
}
}
}
Here is how the IAP section looks in
// MARK: In App Purchases
func setIAP() {
// Set IAPS
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID:NSSet = NSSet(objects: "removeAds, 10000coins")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
}
var list = [SKProduct]()
var p = SKProduct()
func buyProduct() {
print("buy " + p.productIdentifier)
let pay = SKPayment(product: p)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(pay as SKPayment)
}
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
print("product request")
let myProduct = response.products
for product in myProduct {
print("Product added: \(product.productIdentifier), \(product.localizedTitle), \(product.localizedDescription), \(product.price)")
list.append(product)
}
}
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {
print("transactions restored")
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "removeAds":
defaults.setBool(true, forKey: "removeAdsPurchased")
default:
print("IAP not setup")
}
}
let alert = UIAlertController(title: "Thank You", message: "Thankyou for your purchase.", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}
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, .Restored:
print(p.productIdentifier)
let prodID = p.productIdentifier as String
switch prodID {
case "removeAds":
defaults.setBool(true, forKey: "removeAdsPurchased")
case "10000Coins":
defaults.setInteger(bank + 10000, forKey: "bankValue")
bank = defaults.integerForKey("bankValue")
default:
print("IAP not setup")
}
queue.finishTransaction(trans)
break;
case .Failed:
print("buy error")
queue.finishTransaction(trans)
break;
default:
print("default")
break;
}
}
}
func finishTransaction(trans:SKPaymentTransaction) {
print("Transaction finished")
}
func paymentQueue(queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
print("Transaction removed")
}
The way I solved this, seeing as there was no feedback, was very tedious but as follows:
Rather than have two scenes I combined the Menu and options scene so that when the options button is tapped, it runs an action to slide the menu nodes off the screen and options onto the screen. This way the request.delegate = self doesn't change therefore avoiding the crash. Then the only issue was if I launch the app and press the play button quickly to move to the game scene before the request.start finishes it would get the same crash. So I created a loading splash screen to run for 2 seconds in the menu scenes didMoveToView this way it had plenty of time to get all the data before allowing interaction.
I would love to hear of a more simple solution but with the little experience I have this is what I came up with.
Related
I am presenting IAP options in an alert view, and here is the code that I use to do that:
paidAlert.addAction(UIAlertAction(title: "Purchase", style: UIAlertAction.Style.default, handler: { (action) in
IAPHandler.shared.purchaseMyProduct(index: 0)
}))
paidAlert.addAction(UIAlertAction(title: "Restore", style: UIAlertAction.Style.default, handler: { (action) in
IAPHandler.shared.restorePurchase()
}))
paidAlert.addAction(UIAlertAction(title: "Cancel", style: UIAlertAction.Style.cancel, handler: { (action) in
}))
I handle the actual IAP in another Swift file, which is posted here:
import UIKit
import StoreKit
enum IAPHandlerAlertType{
case disabled
case restored
case purchased
func message() -> String{
switch self {
case .disabled:
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
notificationFeedbackGenerator.prepare()
notificationFeedbackGenerator.notificationOccurred(.error)
return "Purchases are disabled on your device."
case .restored:
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
notificationFeedbackGenerator.prepare()
notificationFeedbackGenerator.notificationOccurred(.success)
return "Unlimited strategies have been successfully unlocked. You can restore this purchase using this Apple ID on other devices."
case .purchased:
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
notificationFeedbackGenerator.prepare()
notificationFeedbackGenerator.notificationOccurred(.success)
return "Unlimited strategies have been successfully unlocked. You can restore this purchase using this Apple ID on other devices."
}
}
}
class IAPHandler: NSObject {
static let shared = IAPHandler()
let NON_CONSUMABLE_PURCHASE_PRODUCT_ID = "com.isaranjha.opc.fullversion"
fileprivate var productID = ""
fileprivate var productsRequest = SKProductsRequest()
fileprivate var iapProducts = [SKProduct]()
var purchaseStatusBlock: ((IAPHandlerAlertType) -> Void)?
// MARK: - MAKE PURCHASE OF A PRODUCT
func canMakePurchases() -> Bool { return SKPaymentQueue.canMakePayments() }
func purchaseMyProduct(index: Int){
if iapProducts.count == 0 { return }
if self.canMakePurchases() {
let product = iapProducts[index]
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
print("PRODUCT TO PURCHASE: \(product.productIdentifier)")
productID = product.productIdentifier
} else {
purchaseStatusBlock?(.disabled)
}
}
// MARK: - RESTORE PURCHASE
func restorePurchase(){
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
// MARK: - FETCH AVAILABLE IAP PRODUCTS
func fetchAvailableProducts(){
// Put here your IAP Products ID's
let productIdentifiers = NSSet(objects: NON_CONSUMABLE_PURCHASE_PRODUCT_ID
)
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
productsRequest.delegate = self
productsRequest.start()
}
}
extension IAPHandler: SKProductsRequestDelegate, SKPaymentTransactionObserver{
// MARK: - REQUEST IAP PRODUCTS
func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) {
if (response.products.count > 0) {
iapProducts = response.products
for product in iapProducts{
let numberFormatter = NumberFormatter()
numberFormatter.formatterBehavior = .behavior10_4
numberFormatter.numberStyle = .currency
numberFormatter.locale = product.priceLocale
let price1Str = numberFormatter.string(from: product.price)
print(product.localizedDescription + "\nfor just \(price1Str!)")
}
}
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
purchaseStatusBlock?(.restored)
}
// MARK:- IAP PAYMENT QUEUE
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
if let trans = transaction as? SKPaymentTransaction {
switch trans.transactionState {
case .purchased:
print("purchased")
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
purchaseStatusBlock?(.purchased)
break
case .failed:
print("failed")
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
break
case .restored:
print("restored")
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
break
default: break
}}}
}
}
However, multiple users (but not all users) have reported that they are able to purchase the IAP fine (it charges them and everything), but they do not get any confirmation that I have coded in (alert saying that the purchase was successful etc.) and therefore none of the purchased features. The only workaround we've been able to find is that the user needs to kill the app from multitasking, get to the initial IAP purchase alert again, but select Restore, and then it prompts accordingly and the features are unlocked.
I can't figure out where I am going wrong with this, but seeing as how Restore functionality seems to work, I'm assuming I'm missing something for actual purchasing that will call the case .purchased properly and display the alert.
Any help is appreciated.
The problem that I am experiencing is that the app keeps working if you try to cancel the subscription.
Here is my code snippet:
override func viewDidLoad() {
super.viewDidLoad()
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID: NSSet = NSSet(objects: "Seb.DiPlus.RenewingSubMonthAuto", "Seb.DiPlus.RenewingSubYearAuto")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
}
#IBAction func subscribeMonth(_ sender: Any) {
for product in list {
let prodID = product.productIdentifier
if(prodID == "Seb.DiPlus.RenewingSubMonthAuto") {
p = product
buyProduct()
}
}
}
#IBAction func subscribeYear(_ sender: Any) {
for product in list {
let prodID = product.productIdentifier
if(prodID == "Seb.DiPlus.RenewingSubYearAuto") {
p = product
buyProduct()
}
}
}
#IBAction func restoreComplete(_ sender: UIButton) {
restorePurchases()
}
#IBAction func exit(_ sender: Any) {
_exit(0)
}
func restorePurchases() {
if SKPaymentQueue.canMakePayments(){
print("restored complete")
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
else{
print("restored faild, IAP not activ?")
}
}
func buyProduct() {
print("buy " + p.productIdentifier)
let pay = SKPayment(product: p)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(pay as SKPayment)
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("product request")
let myProduct = response.products
for product in myProduct {
print("product added")
print(product.productIdentifier)
print(product.localizedTitle)
print(product.localizedDescription)
print(product.price)
list.append(product)
}
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
print("transactions restored")
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "Seb.DiPlus.RenewingSubMonthAuto":
print("Subscribe Month!")
if abo < 1 {
readySubscribe()
}
abo = 1
break
case "Seb.DiPlus.RenewingSubYearAuto":
print("Subscribe Year!")
if abo < 1 {
readySubscribe()
}
abo = 1
break
default:
print("IAP not found")
}
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("add payment")
for transaction: AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
print("ERROR: ", trans.error)
switch trans.transactionState {
case .purchased:
print("buy ok, unlock IAP HERE")
print(p.productIdentifier)
let prodID = p.productIdentifier
switch prodID {
case "Seb.DiPlus.RenewingSubMonthAuto":
print("Subscribe Month!!")
if abo < 1 {
readySubscribe()
}
abo = 1
break
case "Seb.DiPlus.RenewingSubYearAuto":
print("Subscribe Year!!")
if abo < 1 {
readySubscribe()
}
abo = 1
break
default:
print("IAP not found")
}
queue.finishTransaction(trans)
case .failed:
print("buy error")
alert(title: "ERROR", message: trans.error?.localizedDescription)
queue.finishTransaction(trans)
break
default:
print("Default")
break
}
}
}
func readySubscribe() {
UserDefaults.standard.setValue(checkSubscribe, forKeyPath: "subscribe")
UserDefaults.standard.synchronize()
self.performSegue(withIdentifier: "readySubscribe", sender: self)
}
func alert (title:String, message:String?){
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
}))
self.present(alert, animated: true, completion: nil)
}
Once I click on restore purchases, you can continue to use the app even though the subscription is no longer activated.
So the function readySubscribe() is called.
You should not activate any purchases in paymentQueueRestoreCompletedTransactionsFinished. This function is called when the restoration process is complete. You can use it to update your UI or alert the user.
The actual restoration of products should be handled in your paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) function.
Restored transactions appear with a state of .restored. You should process them exactly as you process a .purchased state.
Since you are using auto-renewing subscription IAPs you also need to check expiration dates from the receipt and be prepared for new transactions to be presented when the subscription renews. For this reason one of the first things your app should do in didFinishLaunchingWithOptions is create a payment queue observer.
I include a number of non consumable IAPs but I'm encountering issues when loading my app up as it seems to constantly prompt me to log in to my test account user I setup.
I after looking at similar questions, I believe this was because I had originally failed to add the following line to each .case:
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
As you can see from my below code, this is added to each .case. To re-test I created an additional test account, but I'm now getting the exact same results (on each viewDidLoad I am being prompted to log into 2 test accounts...).
Which makes me think that SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction) is not working as expected?
Any help would be appreciated.
override func viewDidLoad() {
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
}
Followed by
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("received response ok");
if product_id == "xxxxxxxxx" {
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)
defaults.setBool(true , forKey: "Purchased")
print("Set key ok")
break;
case .Failed:
print("Purchased Failed");
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
break;
case .Restored:
print("Already Purchased");
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
default:
break;
}
}
}
}
else {
//blah blah
}
}
I am encountering this issue due to the finishTransaction not working as expected, or have the original transaction become "stuck" ?
EDIT:
Here's where I call SKPaymentQueue.defaultQueue().addPayment(payment);
func buyProduct(product: SKProduct){
print("Sending the Payment Request to Apple");
let payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment);
}
func productsRequest (request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
let count : Int = response.products.count
if (count>0) {
var validProducts = response.products
let 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")
}
}
This has "fixed itself". I presume that now I had added:
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
each time I was re-logging in, it was finishing the previously "stuck" transactions, and removing them from the backlog?
Either way; it's resolved now.
I am using IAPHelper to implement in-app purchase, an error is occurring when I go to another view controller after pressing the purchase button. For instance, when I press the purchase button then go to another view controller the error is presented after finishing the purchase work.
class selectQuestion_ViewController: UITableViewController, UITableViewDelegate, UITableViewDataSource{
let helper = IAPHelper(productIdentifiers: NSSet(object: "sppid") as Set<NSObject>)
func purchase(sender: AnyObject)
{
startLoading("Purchasing ..")
self.helper.requestProductsWithCompletionHandler({ (success, products) -> Void in
if success {
self.endLoading()
println("wohooooo")
var sdad = self.helper.productsDict["sppid"]
self.helper.buyProduct(sdad!)
} else {
self.endLoading()
let alert = UIAlertController(title: "Error", message: "Cannot retrieve products list right now.", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
})
}
I recommend you do not use such modules. It would be much better for you and your understanding of IAPs that you check this tutorial and implement the code and methods described there: https://www.youtube.com/watch?v=h1gQklbrgjc
Once you have implemented that code, if something doesn't work (you may have forgotten a line somewhere), come back to SO and we will gladly help you out. Already, as quick help, here is an answer that could help you with most errors once you implement your IAP code with something else than IAP helper: My IAP isn't working. Bugs at func Paymentqueue
In fact, you can watch the video and copy paste the code of my question. My IAP code works ;) Will save you time. But watch the video to understand how to implement IAPs !
I hope this answer helped you ;)
Here is the full IAP code :
import UIKit
import StoreKit
class GameViewController: UIViewController, ADBannerViewDelegate, SKProductsRequestDelegate, SKPaymentTransactionObserver, GKGameCenterControllerDelegate,GADBannerViewDelegate{
#IBOutlet var outRemoveAds: UIButton!
#IBOutlet var outRestorePurchases: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if NSUserDefaults.standardUserDefaults().objectForKey("val") != nil {
print("Has a value.")
banner.removeFromSuperview()
bannerGoogle.removeFromSuperview()
outRemoveAds.removeFromSuperview()
outRestorePurchases.removeFromSuperview()
removeInterFrom = 1
}
else {
print("No Value.")
}
if(SKPaymentQueue.canMakePayments()){
print("IAP is enabled, loading...")
let productID:NSSet = NSSet(objects:"IAP id")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
}
else{
print("Please enable IAPS")
}
}
//IAP Ads
#IBAction func removeAds(sender: UIButton) {
for product in list{
let prodID = product.productIdentifier
if (prodID == "IAP id"){
p = product
buyProduct()
break
}
}
}
#IBAction func restorePurchases(sender: UIButton) {
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
//IAP Functions
var list = [SKProduct]()
var p = SKProduct()
func removeAds(){
banner.removeFromSuperview()
bannerGoogle.removeFromSuperview()
outRemoveAds.removeFromSuperview()
outRestorePurchases.removeFromSuperview()
let theValue = 10
NSUserDefaults.standardUserDefaults().setObject(theValue, forKey: "val")
NSUserDefaults.standardUserDefaults().synchronize()
}
func buyProduct(){
print("Buy: "+p.productIdentifier)
let pay = SKPayment (product: p)
SKPaymentQueue.defaultQueue().addPayment(pay as SKPayment)
}
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
print("Product Request")
let myProduct = response.products
for product in myProduct{
print("Product Added")
print(product.productIdentifier)
print(product.localizedTitle)
print(product.localizedDescription)
print(product.price)
list.append(product as SKProduct)
}
}
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("IAP unlocked")
print(p.productIdentifier)
let prodID = p.productIdentifier as String
switch prodID{
case "IAP id":
print("Remove Ads")
removeAds()
default:
print("IAP not setup")
}
queue.finishTransaction(trans)
break
case .Failed:
print ("Buy error")
queue.finishTransaction(trans)
break
default:
print("default: Error")
break
}
}
}
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {
print("Purchases Restored")
_ = []
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction as SKPaymentTransaction
let prodID = t.payment.productIdentifier as String
switch prodID{
case "IAP id":
print("Remove Ads")
removeAds()
default:
print("IAP not setup")
}
}
}
func finishTransaction(trans:SKPaymentTransaction){
print("Finshed Transaction")
}
func paymentQueue(queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
print("Remove Transaction")
}
}
Put:
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
in viewDidLoad or viewDidAppear
I'm trying to make a non consumable in app purchase. I have all my methods setup and it worked fine a couple of days ago, but now it always fails.
override func viewDidLoad() {
super.viewDidLoad()
//In-App Prurchase Transaction Observer
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
self.getProductInfo() }
In my viewDidLoad I added the transaction observer, and then handled everything else.
//In-App Prurchase
func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) {
let products = response.products
if products.count != 0{
product = products[0] as! SKProduct
}
}
//In-App Prurchase
func getProductInfo(){
if SKPaymentQueue.canMakePayments(){
let productID:NSSet = NSSet(object: "premiunAccountProductID")
let request:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as Set<NSObject>)
request.delegate = self
request.start()
}
}
//In-App Prurchase
#IBAction func goPremiunPressed(sender: AnyObject) {
if IJReachability.isConnectedToNetwork(){//check for internet
let payment:SKPayment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
} else{ // no internet connection
let alertview = JSSAlertView()
alertview.show(self, title: "Sorry", text: "You need to be connected to the internet in order to buy a premium account", color: UIColor(hex: "#e74c3c"), iconImage: UIImage(named: "Cancel"))
alertview.setTextTheme(.Light)
}
}
//In-App Prurchase
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
for transaction:AnyObject in transactions{
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction{
switch trans.transactionState{
case .Purchased:
UserPreferences.userDefaults.setBool(true, forKey: "isPremium")
UserPreferences.userDefaults.synchronize()
println("Transanction Succeded. Is premium is now \(UserPreferences.isPremium)")
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
break
case .Failed:
let alertview = JSSAlertView()
alertview.show(self, title: "Sorry", text: "There was an error completing your transaction, please try again later.", color: UIColor(hex: "#e74c3c"), iconImage: UIImage(named: "Cancel"))
alertview.setTextTheme(.Light)
println("Transaction Failed")
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
break
case .Restored:
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
default:
break
}
}
}
}
So every time I press the "goPremium" button, it takes a couple of seconds and then just goes to the .Failed case. I have tried to make the transaction with multiple sandbox users and they all fail.
I used a tutorial to write all this code so I don't truly know about all the stuff that some of this code is doing and or if this is the most efficient way to make an in app purchase.
Can someone tell me if there is a mistake in my code or if there is a better way to do this.
Thanks!
I'm running Xcode Version 6.4 (6E35b) and this is a simulator error. Just run the application on a real device and it works perfectly.