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.
Related
I am struggling with this for quite some time. First off our problem: In our app we have renewable subscriptions and a one time purchase. I want to read from the receipt if a subscription is still valid or if it is a one time purchase (which is valid lifetime). First off one question:
What does this file contain?
NSData* receiptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
I only have to check if this file is present and if not I request a refresh correct? But if a subscription has been auto renewed do I need to refresh this file as well? Or does the receipt get updated when I verify it with the apple server?
Ok now my process is as follows and starts with the payment queue:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
if(SANDBOX_TESTING) NSLog(#"updated transaction");
[self refreshReceipt];
self.transactionCount = [transactions count];
if(SANDBOX_TESTING) NSLog(#"Number of transactions: %ld", (long)self.transactionCount);
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction restore:NO];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
[[NSNotificationCenter defaultCenter] postNotificationName:IAPProductPurchaseStateChangedNotification object:nil];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Restore transaction started received");
//Only restore transactions that haven't been restored yet AND if they have a still supported identifier
//IMPORTANT: Original Transaction is only set (transaction.originalTransaction.transactionIdentifier) if it is a restore!
//This first if only helps in a restore case, that not all subscription renewals get looped. If a valid subscription is found for one subscription type, the loop runs only once
if(![self.restoredTransactions containsObject:transaction.payment.productIdentifier] && [[self getAllPurchaseIDsForPlatformType:PURCHASESONPLATFORM_IOS] containsObject:transaction.payment.productIdentifier]){
[self completeTransaction:transaction restore:YES];
} else {
self.transactionCount--;
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
//Moved from paymentQueueRestoreCompletedTransactionsFinished
if(self.transactionCount == 0){
[self.delegate restoredTransactions:[self.restoredTransactions count] withReceipt:self.appleReceipt];
}
break;
default:
break;
}
};
}
So in refreshing the receipt I just do this:
-(void)refreshReceipt{
//TODO: Check if this file exists - if not refresh receipt (and only then...)
NSData* receiptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
NSString *payload = [NSString stringWithFormat:#"{\"receipt-data\" : \"%#\", \"password\" : \"%s\"}",
[receiptData base64EncodedStringWithOptions:0], "xxx"];
NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
//Sending the data to store URL based on the kind of build.
NSURL *storeURL;
if(SANDBOX_TESTING){
storeURL = [[NSURL alloc] initWithString:#"https://sandbox.itunes.apple.com/verifyReceipt"];
} else {
storeURL = [[NSURL alloc] initWithString:#"https://buy.itunes.apple.com/verifyReceipt"];
}
//Sending the POST request.
NSMutableURLRequest *storeRequest = [[NSMutableURLRequest alloc] initWithURL:storeURL];
[storeRequest setHTTPMethod:#"POST"];
[storeRequest setHTTPBody:payloadData];
NSError *error;
NSURLResponse *response;
NSData *data = [[NSURLSession sharedSession] sendSynchronousRequest:storeRequest returningResponse:&response error:&error];
if(error) {
_appleReceipt = [NSArray arrayWithObjects: error, nil];
}
NSError *localError = nil;
//Parsing the response as JSON.
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&localError];
//Getting the latest_receipt_info field value.
_appleReceipt = jsonResponse[#"latest_receipt_info"];
if(SANDBOX_TESTING) NSLog(#"Refresh Apple receipt: %#", _appleReceipt);
}
And after I have the receipt I look through it and look for the correct purchase and extract the expiration date or none if it is a lifetime purchase.
BUT: We have some users getting an error message saying that no apple receipt has been returned (triggered by me, if self.appleReceipt = nil) or that no purchase has been found in this receipt. But very few users and I cannot really see what they share in common and where the error is. In testing I never get an error. I also saw live from one user who made the lifelong purchase that no receipt was returned and I don't know why.
So where is my error? Do I have to refresh the receipt everytime? Or why is sometimes my self.appleReceipt empty? Is my process wrong?
When you verify with the Apple server you do get back the latest updates, including any cancellations or expiration.
Sometimes the receipt may take a while to generate after a purchase, which is why occasionally you will see null receipt data especially if you only check for receipt data immediately after a purchase. Or, it could be even possibly be users attempting to hack your application and not really having receipt data.
What you should also be doing is parsing the receipt data Apple returns, looking at the IAP items for your renewable subscriptions to check on when they might be expiring, or for IAP entries for your one-time purchases - you can find a detailed guide on what data is present in the receipt (along with codes you might encounter) here:
https://www.namiml.com/blog/app-store-verify-receipt-definitive-guide
Note that you should really have a server do this receipt check and processing though, as potentially a hacker could intercept that call to Apple for the receipt verification.
Am new to In-app purchase integration in iOS application. I have done the coding in the project level and I have created a Sandbox user in iTunes Connect. I read many tutorials and Apple Document to test the In-App purchase in DEV mode.
As per the document I have removed the APPLE ID from iPad Settings and launched the app from Xcode. But, I didn't received the Account Alert from the app. Also, my products are returning empty in SKProductRequest delegate method didReceiveResponse. I have posted my code for your reference. Can you please help me on this? Am working since last two days. Please help me. Thanks in advance.
- (void) getAvailProducts
{
NSLog(#"Fetching Available Products");
NSSet *productIdentifiers = [NSSet setWithObjects:#"com.test.testios.monthly", #"com.test.testios.yearly" ,nil];
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
self.productsRequest.delegate = self;
[self.productsRequest start];
}
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
//SKProduct *validProduct = nil;
DebugLog(#"\n Products: %#", response.products);
NSUInteger count = [response.products count];
NSLog(#"Request Count: %lu", (unsigned long)count);
if (count > 0)
{
self.validatedProducts = response.products;
DebugLog(#“\n Products: %#", response.products);
self.validationCheck = TRUE;
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
// Check subscription
}
else
{
// No products found….
}
}
Also, I tried with "monthly and yearly" instead of "com.test.testios.monthly and com.test.testios.yearly". But, no results.
Edit: Am getting the mentioned ProductIds are invalid in the following code,
for (NSString *invalidProductId in response.invalidProductIdentifiers)
{
NSLog(#"Invalid product id: %#" , invalidProductId);
}
Thank you all.
Fixed the issue and tested the In-App purchase in Sandbox environment. Following items fixed my issue,
1. iTunes Agreement was not accepted.
2. Tax and Payment details was not added.
3. Added correct Product Identifier in project.
I seem to have trouble understanding how to best deal with the BFTask (Bolts framework) return object for several AWS SDK iOS v2 methods. I am trying to get the name of the region my bucket resides in. Could anyone suggest how to do that given the locationConstraint information I am successfully receiving from the following code? Or is there a generic way to understand what the task.result object contains?
AWSS3TransferManagerUploadRequest *uploadRequest = [AWSS3TransferManagerUploadRequest new];
AWSS3 *myS3 = [[AWSS3 alloc] initWithConfiguration:self.configurationS3];
AWSS3GetBucketLocationRequest *locReq = [AWSS3GetBucketLocationRequest new];
locReq.bucket=#"testAWS";
[[myS3 getBucketLocation:locReq] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id(BFTask *task) {
if(task.error != nil){
NSLog(#"%s Location not found: [%#]",__PRETTY_FUNCTION__, task.error);
} else {
NSLog(#"Location found: [%#] - %li", task.result, [task.result locationConstraint]);
}
return nil;
}];
Also, if anyone has suggestions for tutorials/examples for best understanding BFTask that would be helpful. Thanks for your help. Cheers, Trond
ps. I also asked this question on AWS support site.
Here is the code snippet to determine the bucket location:
AWSS3 *s3 = [AWSS3 defaultS3];
AWSS3GetBucketLocationRequest *getBucketLocationRequest = [AWSS3GetBucketLocationRequest new];
getBucketLocationRequest.bucket = testBucketNameGeneral;
[[s3 getBucketLocation:getBucketLocationRequest] continueWithBlock:^id(BFTask *task) {
if(task.error != nil){
XCTAssertNil(task.error, #"The request failed. error: [%#]", task.error);
}
AWSS3GetBucketLocationOutput *getBucketLocationOutput = task.result;
XCTAssertEqual(getBucketLocationOutput.locationConstraint, AWSS3BucketLocationConstraintBlank);
switch (getBucketLocationOutput.locationConstraint) {
case AWSS3BucketLocationConstraintBlank:
NSLog(#"Classic Region");
break;
case AWSS3BucketLocationConstraintEU:
NSLog(#"EU");
break;
case AWSS3BucketLocationConstraintEUWest1:
NSLog(#"eu-west-1");
break;
case AWSS3BucketLocationConstraintUSWest1:
NSLog(#"us-west-1");
break;
case AWSS3BucketLocationConstraintUSWest2:
NSLog(#"us-west-2");
break;
case AWSS3BucketLocationConstraintAPSoutheast1:
NSLog(#"ap-southeast-1");
break;
case AWSS3BucketLocationConstraintAPSoutheast2:
NSLog(#"ap-southeast-2");
break;
case AWSS3BucketLocationConstraintAPNortheast1:
NSLog(#"ap-northeast-1");
case AWSS3BucketLocationConstraintSAEast1:
NSLog(#"sa-east-1");
break;
case AWSS3BucketLocationConstraintUnknown:
default:
// Error
break;
}
return nil;
}];
However, there is a bug in the SDK, and getBucketLocationOutput.locationConstraint is always AWSS3BucketLocationConstraintUnknown. We are working on the fix right now.
The Bolts-iOS GitHub repo has a pretty good instructions on how to use Bolts.
Hope this helps,
Update
The AWS Mobile SDK for iOS 2.0.4 has been released. The update fixes the issue, and now it returns the correct locationConstraint.
For example: There are two different game characters in a single iPhone device,
we call it A and B.
First, app user login as A, he perform an IAP action, the A logout without the purchase completed.
Then, app user login as B, then purchase finish event arrival, this is the problem, how to judge the receipt belongs to A, not B.
I googled, found SKMutablePayment.requestData may be used for solve this problem, but Apple Document told me this is a reserved property, and must be nil, or else then payment will be reject.
requestData
Reserved for future use. (read-only)
#property(nonatomic, copy, readonly) NSData *requestData Discussion
The default value is nil. If requestData is not nil, your payment
request will be rejected.
Availability Available in iOS 3.0 and later. Declared In SKPayment.h
thanks for everyone's reply
finally, I found SKMutablePayment.applicationUsername can save my private account username,
but It only available on iOS7.0.
save:
SKMutablePayment* payment = [SKMutablePayment paymentWithProduct:product];
payment.applicationUsername = #"my_username_1001";
read:
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction* trans in transactions) {
switch (trans.transactionState) {
case SKPaymentTransactionStateFailed:
[[SKPaymentQueue defaultQueue] finishTransaction:trans];
break;
case SKPaymentTransactionStatePurchased:
// got private user account here
NSString* applicationUsername = [[trans payment] applicationUsername];
break;
case SKPaymentTransactionStatePurchasing:
break;
case SKPaymentTransactionStateRestored:
break;
}
}
}
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.