Reputation: 10748
I just submitted my first app that contains in-app purchases and it was approved last night. I downloaded my app and tried to buy the in-app purchase for testing purposes but to my surprise it crashes when the button that offers the in-app option is tapped. What is more interesting is that it only crashes when downloaded from the Appstore, I deleted the app and re-downloaded it directly from my computer/XCode and the app didn't crash.
Are there any chances that the URL was changed to use the sendbox for testing purposes when the app was in review?
This is the URL I used for production:
let storeURL = NSURL(string: "https://buy.itunes.apple.com/verifyReceipt")
This is the URL I used for testing which was commented out when submitted to the Appstore:
let storeURL = NSURL(string: "https:/sandbox.itunes.apple.com/verifyReceipt")
Again, is there any chance that the URL was changed for testing purposes when the app was in review and left the testing URL?
Is there a way to know what URL is currently in use, in the Appstore?
Thanks
EDITED on 10/18/16:
Here is the code I'm using... Can someone be so kind and give it a quick look to see if something is wrong, especially the accessPremiumFeature
method which is the one being called when the crash is happening?
FYI - I still don't have error details because I cannot reproduce it locally and the app was approved hours ago.
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 occurs when accessPremiumFeature
is called. Is there anything wrong here?
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 purchase 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)
}
}
Upvotes: 0
Views: 462
Reputation: 116
You could try Charles HTTP Proxy and watch which URL is requested or you open the App with a hex-viewer and search for the string.
Upvotes: 1