Our app provides in app purchases to apple hosted extra content which the user downloads, but some users are reporting problems with the download.
It seems to fail halfway through, alert the user and resets all buttons etc to allow the user to buy again (for free as they have already purchased). If the user then tries to re buy or restore purchases in the app it still fails. The following code is what handles a failed state.
-(void) paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads{
for (SKDownload *download in downloads){
switch (download.downloadState){
case SKDownloadStateActive:{
//code removed for post
break;
}
case SKDownloadStateCancelled:{ break; }
case SKDownloadStateFailed:
{
//log the error
NSLog(#"Transaction error: %#", download.transaction.error.localizedDescription);
//let the user know
[[[UIAlertView alloc] initWithTitle:#"Error!" message:[NSString stringWithFormat:#"%# download failed, please try again later", download.contentIdentifier] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
//post notification - caught in view controller for updating buy buttons etc
[[NSNotificationCenter defaultCenter] postNotificationName:IAPHelperDownloadFailedNotification object:download userInfo:nil];
// This should delete the download assets from the Cache folder.
NSError* error = nil;
[[NSFileManager defaultManager] removeItemAtURL:download.contentURL error:&error];
if(error){
//
}
//finish the transaction
[[SKPaymentQueue defaultQueue] finishTransaction:download.transaction];
break;
}
case SKDownloadStateFinished:{
//code removed for post
break;
}
case SKDownloadStatePaused:{
break;
}
case SKDownloadStateWaiting:{
break;
}
}
}
I've looked around stack overflow and elsewhere and what little examples I can find on in app purchases all do the same as above. Any help or info would be great.
I got similar problem and just found a possible resolution in Apple's FAQ on IAP:
Q: How do I resolve the "You've already purchased this In-App Purchase but it hasn't been downloaded." error message?
A: You are getting the "You've already purchased this In-App Purchase but it hasn't been downloaded." error message because you did not call SKPaymentQueue 's finishTransaction:method in your application. Calling finishTransaction: allows you to remove a transaction from the payment queue.
I think I will ask my IAP engine to check whether there is any IAP is being processed when app exits(my problem occurs when user stopped the app while downloading), if yes, calls finishTransaction:.
Hope it helps.
Related
I have successfully implemented "In App Purchase" in my project and now i am trying to implement a "Touch Id" to buy a product except asking a "Sign in to iTunes" popup. Here in my code it asks "touch id" and "Sign in to iTunes" popup both one after another, why this one wired both things i want only "touch id" to buy product. I have gone for IAP How do you add an in-app purchase to an iOS application? and for Touch ID How do i integrate Touch ID for my IAP? but not getting proper way to implement a touch id for IAP.Here is the code i have added,
Is this possible through code or it shows apple at the time of buying a product from app store.
Check above snapshot,
#import <LocalAuthentication/LocalAuthentication.h>
-(void) localAunthentication
{
//LocalAuthentication
LAContext *authContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *authLocalizedReasonString = #"Please aunthenticate to proceed";
if ([authContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError])
{
[authContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:authLocalizedReasonString
reply:^(BOOL success, NSError *error)
{
if (success)
{
// Allocated here for succinctness.
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperationWithBlock: ^
{
}];
}
else
{
switch (error.code)
{
case LAErrorAuthenticationFailed:
//Authentication Failed
break;
case LAErrorUserCancel:
break;
case LAErrorUserFallback:
break;
default:
//Touch ID is not configured
break;
}
NSLog(#"%#",error.localizedDescription);
}
}];
}
else
{
}
}
What should i do to asks only "Touch Id" instead of "Sign in To iTunes" popup.
The iTunes Store controls what kind of authentication it presents, not your app. If necessary, an alert will be presented, otherwise the TouchID/FaceID overlay.
The reason you're never seeing the TouchID/FaceID overlay is that during development, your app is not using the real iTunes Store but a sandbox. The sandbox environment, AFAIK, always presents an alert.
I am testing in-app purchases for the new version of our app, the in-app purchase code was tested before and it was working ok however now it asks for itunes sign-in twice before the purchase confirmation alert. It still works fine and purchases the item correctly after the double sign-in but it is a little bit disturbing. Did anybody have a similar issue lately with sandbox servers?
To give some more info actual transaction verification happens on our server, I'm using RMStore for default dummy verification on client side, it actually does nothing but checks if productID exists in app receipt, the cool side is it refreshes app receipt if it is nil or productID does not exist in the receipt. There are only auto-renewing subscriptions in our app, and I logout from appstore before testing a new purchase.
This roughly how my code looks like:
-(void) paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
{
RMStoreAppReceiptVerificator *verificator = [RMStoreAppReceiptVerificator new];
[verificator verifyTransaction:transaction success:^{
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
NSString *receiptStr = [receipt base64EncodedStringWithOptions:kNilOptions];
[self sendReceiptToServer:receiptStr];
}
failure:^(NSError *error) {
...
}];
}
break;
case ... : ...
default: ...
}
};
}
To get the login to work you have to logout of the settings > itunes. Then login when the app prompts you too using the test account (created on itunes connect).
Its the first time i use unibill for my ios game and i got an issue when i try to build and run my app onto the device.
I get the message, that there is a use of undeclared idenifier for the case SKPaymentTransactionStateDeferred: (i didnt changed anything on the unibill plugin sourcecode) so iam not sure what to do.
i cant post an image so i post the code below. Pls watch on the line XCODE ISSUE.
// The transaction status of the SKPaymentQueue is sent here.
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray*)transactions {
NSLog(#"Unibill: updatedTransactions");
for(SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
// Item is still in the process of being purchased
break;
case SKPaymentTransactionStatePurchased:
case SKPaymentTransactionStateRestored: {
// Item was successfully purchased or restored.
NSMutableDictionary* dic;
dic = [[NSMutableDictionary alloc] init];
[dic setObject:transaction.payment.productIdentifier forKey:#"productId"];
[dic setObject:[self selectReceipt:transaction] forKey:#"receipt"];
NSData* data;
data = [NSJSONSerialization dataWithJSONObject:dic options:0 error:nil];
NSString* result;
result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
UnitySendMessage(UNITY_GAMEOBJECT_NAME, "onProductPurchaseSuccess", result.UTF8String);
#if !__has_feature(objc_arc)
[result release];
[dic release];
#endif
// After customer has successfully received purchased content,
// remove the finished transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
break;
}
case SKPaymentTransactionStateDeferred:
**//XCODE ISSUE: USE OF UNDECLARED IDENTIFIER "SKPaymentTransactionStateDeferred"**
NSLog(#"Unibill: purchaseDeferred");
UnitySendMessage(UNITY_GAMEOBJECT_NAME, "onProductPurchaseDeferred", transaction.payment.productIdentifier.UTF8String);
break;
case SKPaymentTransactionStateFailed:
// Purchase was either cancelled by user or an error occurred.
NSString* errorCode = [NSString stringWithFormat:#"%d",transaction.error.code];
if (transaction.error.code != SKErrorPaymentCancelled) {
NSLog(#"Unibill: purchaseFailed: %#", errorCode);
UnitySendMessage(UNITY_GAMEOBJECT_NAME, "onProductPurchaseFailed", transaction.payment.productIdentifier.UTF8String);
} else {
NSLog(#"Unibill: purchaseFailed: %#", errorCode);
UnitySendMessage(UNITY_GAMEOBJECT_NAME, "onProductPurchaseCancelled", transaction.payment.productIdentifier.UTF8String);
}
// Finished transactions should be removed from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
break;
}
}
}
does someone know what i should do? another question: where can i find the declaration of the cases called in the unibillstorekit.mm file? Or where do i have to declare this identifier?
please tell me if you need more specific informations.
note: iam just a beginner :)
Have you changed your deployment target?
The problem can be due to your deployment target pointing to iOS version below 8.0. SKPaymentTransactionStateDeferred is only available in iOS version greater to 8. So if your deployment target is set to e.g. 7.1 you'll run into this error.
To get rid of this you can just update your deployment target to be greater or equal to 8.0 or use another (older) version of the library or plugin, repsectively
Hope this helps.
I am working on IAP first time i have not uploaded any version on app store we have included IAP in first version. i have submitted IAP Three times but still everytime we get Developer action needed status. I have created test user and working good in sandbox env. Now if i have uploaded app on store will IAP works??
Belo code got information in sandbox env now what to do for actual working means when i uplaod app on store will it work same as sandbox mode,
-(void)productsRequest:(SKProductsRequest *)request
didReceiveResponse:(SKProductsResponse *)response
{
SKProduct *validProduct = nil;
int count = [response.products count];
if (count>0) {
validProducts = response.products;
validProduct = (response.products)[0];
if ([validProduct.productIdentifier
isEqualToString:kTutorialPointProductID]) {
NSLog(#"All product title is --%#",validProduct.localizedTitle);
NSLog(#"All product prize is %#", validProduct.price);
NSLog(#"All des is %#",validProduct.localizedDescription);
NSLog(#"response products is %#", validProduct.productIdentifier);
NSLog(#"AL*****%#", validProduct);
}
} else {
UIAlertView *tmp = [[UIAlertView alloc]
initWithTitle:#"Not Available"
message:#"No products to purchase"
delegate:self
cancelButtonTitle:nil
otherButtonTitles:#"Ok", nil];
[tmp show];
}
}
Goto iTunes connect- >Manage APPs -> Manage In app Purchase -> Select your in app purchase
Make sure the product Cleared for Sale Yes and add screenshot for review.
Make sure IN- APP Purchase Details have Proper Display Name and Description
In iTunes connect- > Manage APPs -> View Details of the app.
Below Demo Account Information (Optional)
There is IN APP PURCHASE.You need to select the appropriate in app and save before You proceed with Ready to Submit.
if your in app purchase works fine with SandBox account then it is working fine !!!
I just upgraded my app from Facebook iOS SDK 3.1 to 3.2.1 and I'm trying to take advantage of the new error handling provided by the new FBError category on NSError. The code is at the bottom. It compiles fine, but when a FB error occurs, I get the following at run time:
- [NSError fberrorShouldNotifyUser]: unrecognized selector sent to instance
This seems like a linker error, where the category is not getting linked in from the the FacebookSDK static library. I tried adding both the -ObjC and -all_load flags under the other linker flags in the target. I read this: http://developer.apple.com/library/mac/#qa/qa1490/ but still no luck.
Basically the same code works fine in the sample projects provided by Facebook. Thanks for any suggestions.
// Open the Facebook session.
- (void)openSession {
NSArray *permissions = [[NSArray alloc] initWithObjects:#"email", nil];
// Open or re-open the active session
[FBSession openActiveSessionWithReadPermissions:permissions
allowLoginUI:YES
completionHandler:^(FBSession *session, FBSessionState state, NSError *error) {
[self sessionStateChanged:session state:state error:error];
}];
}
- (void)handleAuthError:(NSError *)error{
NSString *alertMessage, *alertTitle;
if (error.fberrorShouldNotifyUser) {
// If the SDK has a message for the user, surface it. This conveniently
// handles cases like password change or iOS6 app slider state.
alertTitle = #"Something Went Wrong";
alertMessage = error.fberrorUserMessage;
} else if (error.fberrorCategory == FBErrorCategoryAuthenticationReopenSession) {
// It is important to handle session closures as mentioned. You can inspect
// the error for more context but this sample generically notifies the user.
alertTitle = #"Session Error";
alertMessage = #"Your current session is no longer valid. Please log in again.";
} else if (error.fberrorCategory == FBErrorCategoryUserCancelled) {
// The user has cancelled a login. You can inspect the error
// for more context. For this sample, we will simply ignore it.
NSLog(#"user cancelled login");
} else {
// For simplicity, this sample treats other errors blindly, but you should
// refer to https://developers.facebook.com/docs/technical-guides/iossdk/errors/ for more information.
alertTitle = #"Unknown Error";
alertMessage = #"Error. Please try again later.";
NSLog(#"Unexpected error:%#", error);
}
if (alertMessage) {
[[[UIAlertView alloc] initWithTitle:alertTitle
message:alertMessage
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil] show];
}
}
// Handle Facebook session state changed
- (void)sessionStateChanged:(FBSession *)session
state:(FBSessionState)state
error:(NSError *)error {
if (error) {
[self handleAuthError:error];
} else {
switch (state) {
case FBSessionStateOpen:
[self onSessionOpen:session];
break;
case FBSessionStateOpenTokenExtended:
[self onSessionOpen:session];
break;
case FBSessionStateClosedLoginFailed:
[self onSessionClose:error];
break;
case FBSessionStateClosed:
// No-op
// See: https://developers.facebook.com/docs/reference/ios/3.1/class/FBSession
// Session is closed but token is still cached for later use.
break;
default:
NSLog(#"sessionStateChanged: unknown state: %d", state);
break;
}
}
}
UPDATE:
A friend advised that I check if the selector actually exists in the linked binary. I followed the instructions here to find the location of the debug binary in the finder: Where is my application binary in XCode?
Then, I right-clicked on MyApp.app and chose "Show Package Contents". Found the binary file (it was the largest file in the list), dragged it into Vim and searched for "fberrorShouldNotifyUser". I couldn't find this selector or any of the FBError selectors.
I also tried clearing XCode's derived data - still no luck.
UPDATE #2:
Ugh, sometimes you totally miss the obvious answer. It turns out I didn't have the -ObjC flag properly set for my debug builds. See screenshot:
Thanks to d.kendall for getting me to check this again.
You need to add -ObjC to "other linker flags" in your project's build settings.
Did you install the 3.2.1 sdk into a different directory (rather than overwriting the 3.1 sdk)? If so, it's possible xcode is linking the older version. Can you confirm the framework path in xcode by:
In project navigator where you added the Facebook Framework, right click -> Show in Finder and verify it opens the 3.2.1 sdk location; and,
In your target's build settings, search for "framework" and verify the Framework Search Paths only includes the 3.2.1 sdk path - I have found that it can remember old framework locations and use the the wrong path.