SKPaymentTransactionStateFailed, but no NSError - storekit

We have an app with IAP coin purchases. We've recently received mails from a few of our users, saying that they couldn't purchase any coins. I looked at their session logs and saw Failed IAP events, each with no fail reason logged. Here's the related code:
- (void)_purchaseRequestFailed:(SKPaymentTransaction *)transaction state:(StoreTransactionState)state error:(NSError *)error
{
IAPProduct *product = [self getProductWithId:transaction.payment.productIdentifier];
if (error.code==SKErrorPaymentCancelled) {
[_metricsManager logFailIAP:product failReason:#"Payment canceled"];
} else {
[_metricsManager logFailIAP:product failReason:error.localizedDescription];
}
if ([_delegate respondsToSelector:#selector(didSucceedPurchasingProduct:)]) {
[_delegate didFailPurchasingProduct:product];
}
}
inside logFailIAP, I log things like time, UDID, event name, and the error.localizedDescription.
if (failReason != nil && failReason.length > 0) {
[metricsDictionary setObject:failReason forKey:MetricsEventParameterFailReason];
}
In the logs, I'm getting Failed IAP events, but no logged reason for failing. Is there any case where, you get "SKPaymentTransactionStateFailed", but, have an empty error.localizedDescription? I can confirm that the logs work, and have seen errors like "Payment canceled", and "Cannot connect to iTunes" being logged in other devices. The issue is not specific to a device or iOS.

I have encountered a nil error in the following scenario:
User updates iOS on their device.
Immediately after the update, the user goes into your app and tries to make a purchase.
Instead of the usual purchase dialog, they get an "iTunes terms of service have changed. You need to accept new T&C's" (or something along the lines), which redirects them to the iTunes app and shows them the new T&C's. The payment is then cancelled and you get a nil error, and, obviously, a nil error.localizedDescription.
For this scenario to happen, your app needs to be the user's first point of interaction with the iTunes Store after the system update.

Related

Why does Xcode simulator launch app with didEnterRegion call, while iPhone does not?

I'm troubleshooting a location-based app launch cycle that works fine on iOS simulator, but apparently does not launch the app on an actual device.
I've set up UNNotifications for both enter and exit events. Both the simulator and the device(s) register and display these notifications.
The next thing that is supposed to happen is that the app goes through a launch process, setting the app's state in such a way that I can tell without being connected to the debugger whether the app has launched or not when I open it from the home screen.
On simulator, the didEnterRegion code gets called and I can step through the subsequent launch code using the debugger - success.
However when I take the device out (for a walk) all I get are UNNotifications, and no app launch (which I can tell from the UI in the app on the real device)
I'm sure I need to improve my testing strategy (welcome to suggestions!), but at this point I should be able to expect that the app should behave the same on the simulator and the actual device - it is not.
Why is the expected outcome happening on the simulator but not on the device?
LocationService.swift
func handleRegionEnterEvent (for region: CLRegion) {
ApplicationController.sharedInstance.didEnterRegion(region)
}
func handleRegionExitEvent (for region: CLRegion) {
ApplicationController.sharedInstance.didExitRegion(region)
}
ApplicationController.swift
func didEnterRegion (_ region: CLRegion) {
// Present Alert
delegate?.handleUNEvent(note: "ENTER: \(region.identifier)")
// Start launch cycle
}
AppDelegate.swift
extension AppDelegate: AppControllerDelegate {
func handleUNEvent(note: String?) {
// Show an alert if application is active
if UIApplication.shared.applicationState == .active {
guard let message = note else { return }
Alert().showAlert(withMessage: "Application received UN while active: \(message)", title: "UNNotification")
} else {
// Otherwise present a local notification
guard let body = note else { return }
let notificationContent = UNMutableNotificationContent()
notificationContent.body = body
notificationContent.sound = UNNotificationSound.default
notificationContent.badge = UIApplication.shared.applicationIconBadgeNumber + 1 as NSNumber
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: "location_change",
content: notificationContent,
trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Error: \(error)")
}
}
}
}
}
The simulator sadly has a lot of differences from a device both in terms of data reported (you will not get speed for example) and also as you saw call flow may be different...
Something to check is to make sure you have the descriptions for the specific location permissions you are asking for in relation to location set up. For entering regions you probably want to make sure you have "always" access.
It may also be worth enabling wireless debugging, and walking around with laptop tethered to the device to verify with the debugger the didEnterRegion is truly not called, and there is a bug in the code that is supposed to display the UI you are expecting. At the very least perhaps you could get better logging as to the status of various location callbacks.
What happened: Code that executed on simulator was indeed crashing on the device and I was not reviewing crash logs from the device. This question arose from an issue that is neither ios, or core-location specific.
It has everything to do with choosing appropriate testing strategies and using all available information at one's disposal before coming to what may turn out to be premature conclusions.
However when I take the device out (for a walk) all I get are UNNotifications, and no app launch (which I can tell from the UI in the app on the real device)
I was mistaken to think that a) User Notifications that appear when the App is not running are presented by the system (as banners) and b) the App launches in another process. In fact my own code specifically executes the code that makes those notifications appear. Face-to-palm: Copying code from tutorials to achieve quick test results without even trying to understand what the code does can lead to that I suppose.
In Xcode, plug in your device and hit command-shift-2 to open Devices & Simulators window. Select your device on the left and hit "View Logs" button to bring up your crash log.

Is there a way to force a bot to always accept permission alerts under ui tests in xcode continuous integration?

I need to run continuous integration on xcode server using a lot of simulators. Is there a way to force it to always accept permission alerts like:
Allow "App" to access your photos
and so on...
In your setUp() method, create an interruption monitor and handle the alert by tapping the OK button. This means that whenever you try to interact with the app, a check will be made to see if the permissions view is in the way, and tap the OK button.
let permissionInterruptionMonitor = addUIInterruptionMonitor(withDescription: "Photos permission alert") { (alert) in
alert.buttons["OK"].tap()
return true // The interruption has been handled
}
If there are other alerts that may appear in your app with an OK button, but you don't want those to be automatically handled, you should make sure that the interruption monitor handler checks that it is the alert that you want to handle.
let permissionInterruptionMonitor = addUIInterruptionMonitor(withDescription: "Photos permission alert") { (alert) in
if alert.staticTexts["\"AppName\" Would Like To Access Your Photos"].exists {
alert.buttons["OK"].tap()
return true // The interruption has been handled
}
return false // The interruption has not been handled
}

Game Center not asking my TestFlight users to log in?

In my app, I use the following code in the view controller in my sprite kit project to authenticate a player using GameKit:
-(void)autheticateLocalPlayer{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error){
if (error != nil){
NSLog(#"%#", [error localizedDescription]);
}
if (viewController != nil){
[self presentViewController:viewController animated:YES completion:nil];
}else{
if ([GKLocalPlayer localPlayer].authenticated){
_gameCenterEnabled = YES;
[[GKLocalPlayer localPlayer]loadDefaultLeaderboardIdentifierWithCompletionHandler:^(NSString *leaderboardIdentifier, NSError *error) {
if (error != nil){
NSLog(#"%#", [error localizedDescription]);
}
else{
_leaderboardIdentifier = leaderboardIdentifier;
}
}];
}
else{
_gameCenterEnabled = NO;
NSLog(#"game center not available");
}
}
};
}
It works fine on my phone, but after building an Ad-Hoc build with a provisioning profile including a friend's phone to see how it would work, they were not asked to log in when they opened the app, whereas my phone was (and all simulator phones were).
On top of that, attempting to open the leaderboards would simply show a message "game center is not available, user is not logged in" or something like that.
I went to this friend's phone's Game Center settings, and verified that sandbox mode was ON. After that, I manually logged in to a Sandbox tester account I made in iTunes connect, and once I opened my app again, (on their phone), it displayed the "welcome back" message. However, the leaderboards showed "no scores", when I believe that my score should be on there.
My questions are:
1. Why were they not asked to log in? Is it a problem with my code, or is that how it's supposed to work when beta testing with sandbox mode on?
2. Why doesn't it show the other scores? My leaderboard is in iTunes connect, and the leaderboard name is properly displayed in the app, but no score are visible.
Thanks for any help!
Apparently, it takes about 10 hours before the other user's scores will be updated. Today, all of my test users' scores are on game center. Would be nice if this was made more obvious, so developers didn't think Game Center wasn't working.

How to give an app access to Accounts that was previously denied?

I've been playing with Accounts framework and during my tests I've denied the app access to Accounts. Now the following code returns Denied each time I run it.
[[self accountStore] requestAccessToAccountsWithType:twitterType options:nil completion:^(BOOL granted, NSError *error) {
if (granted) {
accounts = [[self accountStore] accountsWithAccountType:twitterType];
} else if (error != nil) {
NSLog(#"Failed to list Twitter accounts %#", error);
} else {
NSLog(#"Access denied");
}
}];
What sucks is I have no clue where to reset it and googling doesn't help. I've seen how to fix it on iOS but I'm running a Mac app and the solution doesn't apply.
The Account Framework API documentation is not helpful at all.
Palm face.
Figured it out, I've found a file with my app id called "~/Library//Application Support/com.apple.TCC/TCC.db"
It's a sqlite3 file, so run sqlite3 "~/Library//Application Support/com.apple.TCC/TCC.db" then search for your app in "access" table, delete the row and should you have access again.
If that doesn't help there's also a file called ~/Library/Accounts/Accounts3.sqlite, there ZAUTHORIZATION table which includes your app.
The proposed solution no longer works, because of high-profile hacks made by big companies and others. This solution has never been supported and is an invitation to hackers. The right way to do this is using the tool provided by Apple: tccutil.
Thus, do the following:
Open Terminal.
Run tccutil reset <service> [bundle_id], in which <service> can be AddressBook or Calendar or All with the optional ID of the bundle you want to reset.
Example:
tccutil reset All com.apple.Terminal

xcode project always gets facebook login fail status

I encountered a facebook login problem. I installed FB SDK 3.0 and sample projects seemly run well --- not quite sure though. When I create a button in my own project and try to log in to Facebook, I always get FBSessionStateClosedLoginFailed from sessionStateChanged method, although I apparently get the page "You have already authorized this app. Press Okay to continue". I also tried log out through Simulator Safari facebook page, and re-launched my project and I got new username/password page and I then logged in, but I still get FBSessionStateClosedLoginFailed!
A few more observations.
1, I got FBSessionStateClosedLoginFailed thru NSLog, the log comes up right after I clicked Okay button on the Facebook page "You have already authorized this app.
2, I tried clean xcode project cache, but no help.
Kind of frustrated. I wonder if any of you can help on this problem. Appreciated!
-Tony
Addition:
I did a bit more research, and I found what I have is probably a typical problem, as Facebook highlight here ("Pro-tip 3: Complete all iOS App Settings" section in page http://developers.facebook.com/docs/howtos/login-with-facebook-using-ios-sdk/). The image with a big red cross over iPhone is exactly what I have. From xcode project, I click my login button, and I'm directed to this page, and every time I have to click "Okay" on this page (let me call it Okay_page) to go back to my app in Simulator, however with login status as FBSessionStateClosedLoginFailed. I checked bundle ID, I have it correct. I don't have a 'YOUR_IPHONE_APP_STORE_ID' yet, which I believe I need pay Apple to get and I will do it later but not while I'm still working on it.
Now get a couple of questions:
1, Do I have to have YOUR_IPHONE_APP_STORE_ID to skip this Okay_page?
2, Does this (not automatically re-directed back to my app from this Okay_page) have anything to do with that I always get a login status of FBSessionStateClosedLoginFailed when I manually click Okay in this Okay_page and return to my app in Simulator? In another word, is (that I have to manually click Okay to get back to my app in Simulator) (the reason I got a status of FBSessionStateClosedLoginFailed)?
Did you have set this function in AppDelegate?
-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
return [FBSession.activeSession handleOpenURL:url];
}
I had the same issue:
We created the facebook app (developer.facebook.com) setting our
bundle id (ex. com.MyCompany.MyApp)
Off course, created our iOS app with the same bundle id
After delivered our app to the client, he changed the bundle id
without notify us.
Obviously he started to get FBSessionStateClosedLoginFailed errors!
So, you probably have different bundle id's between your iOS project and your Facebook App.
I had this error and couldn't resolve it for quite some time. It turns out that if you are using the native iOS Facebook account in settings and choose "no" for allow Facebook for this app, the app is blocked by the OS. You will always get the FBSessionStateClosedLoginFailed error and there is nothing you can do inside the code to fix this (99% sure). The best you can do is alert the user to check out the settings.
i am typing wrong line in given below method. self.session was nil
return [self.session handleOpenURL:url];
it should be
return [[FBSession activeSession] handleOpenURL:url];
Use Following Method.
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// attempt to extract a token from the url
return [[FBSession activeSession] handleOpenURL:url];
// return [self.session handleOpenURL:url];
}
I fixed this problem after I reset the simulator. iOS Simulator - Reset Content and Settings..., hope to help.
I had this issue because I was calling [FBSession.activeSession close] in my applicationDidEnterBackground: app delegate callback. This closed the session so that when the redirect brought the app back up to open the session, it failed because it had been put into the closed state.
Make sure that you're not closing the session anywhere before you get to the Okay page.
go to Facebook.com. Open your application on the left panel -> Status & Review -> there is a toggle with text "Do you want to make this app and all its live features available to the general public?". Turn it on
well. I followed FBLoginCustomUISample somewhere on github.com and it was a working example of facebook login implementation from facebook itself. You can also read my workaround while creating facebook login feature in a iOS app. Here is a link form me:
Native iOS Facebook SSO won't return to app

Resources