@PBK -
EDIT: I just checked in iTunesConnect in the In-App Purchases page and it has a status of "Missing Metadata" Could this be whats causing my issue? But why was the app approved if something was missing? It has a yellow dot in the Localization section under English(US). The one thing I didn't add are th screenshots because I'm not sure what type of screenshots are needed in this section.
Thank you for your reply. The way I tested it was using the sendbox URL and commenting the production URL. I do use different user account for each testing enviroment. Let me clearify something, the crash ocurrs as soon as a button offering the in-app purchase is tapped. Here is all of the code I'm usign for my in-app process. Woud you or someone else be so kind and double check it to see if there is something that could be improved? I know I know...
Based on the code below the crash happens as soon as the accessPremiumFeature from the SettingsViewController is tapped.
I'm not sure if this IF statement is the one causing the problem because as I mentioned I cannot reproduce the promblem locally, it doesn't crash when the app is loaded directly from XCode.
IF statement from SettingsViewController.swift
if NSUserDefaults.standardUserDefaults().boolForKey("com.domain.appName"){
}else{
}
AppDelegate.swif
import StoreKit
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var canPurchase:Bool = false
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if SKPaymentQueue.canMakePayments(){
self.canPurchase = true
IAPManager.sharedInstance.setupInAppPurchases()
}
return true
}
}
SettingsViewController.swift - Here is where the crash occurrs when accessPremiumFeature is tapped.
import StoreKit
class SettingsViewController: UIViewController {
@IBAction func accessPremiumFeature() {
if NSUserDefaults.standardUserDefaults().boolForKey("com.domain.appName"){
let alert = UIAlertController(title: "PRO-Version", message: "You already have the PRO version.", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}else{
var productInfo:SKProduct?
for product in IAPManager.sharedInstance.products{
productInfo = product as? SKProduct
}
let alertController = UIAlertController(title: "Premium Features", message: "Unlock all premium features for \(productInfo!.price)." + "This includes... bla, bla, bla...", preferredStyle: .Alert)
alertController.view.tintColor = UIColor.myRedColor()
let okAction = UIAlertAction(title: "Ok", style: .Default, handler: nil)
let buyAction = UIAlertAction(title: "Buy", style: .Default) { (action) -> Void in
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("StoreTableView") as! StoreTableViewController
self.presentViewController(vc, animated: true, completion: nil)
}
alertController.addAction(okAction)
alertController.addAction(buyAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
}
}
IAPManager.swift - This is the main in-app pruchase code (Brain).
import StoreKit
// protocol to notify when user restores purchase
protocol IAPManagerDelegate {
func managerDidRestorePurchases()
}
class IAPManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver, SKRequestDelegate {
static let sharedInstance = IAPManager()
var request:SKProductsRequest!
var products:NSArray!
var delegate:IAPManagerDelegate?
//Load product identifiers for store usage
func setupInAppPurchases(){
self.validateProductIdentifiers(self.getProductIdentifiersFromMainBundle())
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
}
//Get product identifiers
func getProductIdentifiersFromMainBundle() -> NSArray {
var identifiers = NSArray()
if let url = NSBundle.mainBundle().URLForResource("iap_product_ids", withExtension: "plist"){
identifiers = NSArray(contentsOfURL: url)!
}
return identifiers
}
//Retrieve product information
func validateProductIdentifiers(identifiers:NSArray) {
let productIdentifiers = NSSet(array: identifiers as [AnyObject])
let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
self.request = productRequest
productRequest.delegate = self
productRequest.start()
}
func createPaymentRequestForProduct(product:SKProduct){
let payment = SKMutablePayment(product: product)
payment.quantity = 1
SKPaymentQueue.defaultQueue().addPayment(payment)
}
func verifyReceipt(transaction:SKPaymentTransaction?){
let receiptURL = NSBundle.mainBundle().appStoreReceiptURL!
if let receipt = NSData(contentsOfURL: receiptURL){
//Receipt exists
let requestContents = ["receipt-data" : receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))]
//Perform request
do {
let requestData = try NSJSONSerialization.dataWithJSONObject(requestContents, options: NSJSONWritingOptions(rawValue: 0))
//Build URL Request
let storeURL = NSURL(string: "https://buy.itunes.apple.com/verifyReceipt")// production URL
//let storeURL = NSURL(string: "https:/sandbox.itunes.apple.com/verifyReceipt") // Testing URL
let request = NSMutableURLRequest(URL: storeURL!)
request.HTTPMethod = "Post"
request.HTTPBody = requestData
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: { (responseData:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
do {
let json = try NSJSONSerialization.JSONObjectWithData(responseData!, options: .MutableLeaves) as! NSDictionary
print(json)
if (json.objectForKey("status") as! NSNumber) == 0 {
if let latest_receipt = json["latest_receipt_info"]{
self.validatePurchaseArray(latest_receipt as! NSArray)
} else {
let receipt_dict = json["receipt"] as! NSDictionary
if let purchases = receipt_dict["in_app"] as? NSArray{
self.validatePurchaseArray(purchases)
}
}
if transaction != nil {
SKPaymentQueue.defaultQueue().finishTransaction(transaction!)
}
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
self.delegate?.managerDidRestorePurchases()
})
} else {
//Debug the receipt
print(json.objectForKey("status") as! NSNumber)
}
} catch {
print(error)
}
})
task.resume()
} catch {
print(error)
}
} else {
//Receipt does not exist
print("No Receipt")
}
}
func validatePurchaseArray(purchases:NSArray){
for purchase in purchases as! [NSDictionary]{
self.unlockPurchasedFunctionalityForProductIdentifier(purchase["product_id"] as! String)
}
}
func unlockPurchasedFunctionalityForProductIdentifier(productIdentifier:String){
NSUserDefaults.standardUserDefaults().setBool(true, forKey: productIdentifier)
NSUserDefaults.standardUserDefaults().synchronize()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
func lockPurchasedFunctionalityForProductIdentifier(productIdentifier:String){
NSUserDefaults.standardUserDefaults().setBool(false, forKey: productIdentifier)
NSUserDefaults.standardUserDefaults().synchronize()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
//MARK: SKProductsRequestDelegate
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
self.products = response.products
print(self.products)
}
// MARK: SKPaymentTransactionObserver Protocol
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions as [SKPaymentTransaction]{
switch transaction.transactionState{
case .Purchasing:
print("Purchasing")
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
case .Deferred:
print("Deferrred")
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
case .Failed:
print("Failed")
print(transaction.error?.localizedDescription)
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
case.Purchased:
print("Purchased")
self.verifyReceipt(transaction)
case .Restored:
print("Restored")
}
}
}
func restorePurchases(){
let request = SKReceiptRefreshRequest()
request.delegate = self
request.start()
}
func requestDidFinish(request: SKRequest) {
self.verifyReceipt(nil)
}
}