SKPaymentTransactionObserver - transaction fail - ios

I am working on an app with in-app-purchase up to the transaction part.
I have a purchaseViewController to call up getProductInfo while launch:
-(void)getProductInfo: (CC3DeviceCameraOverlayUIViewController *) viewController
{
_homeViewController = viewController;
if ([SKPaymentQueue canMakePayments])
{
SKProductsRequest *request = [[SKProductsRequest alloc]
initWithProductIdentifiers:
[NSSet setWithObject:self.productID]];
request.delegate = self;
[request start];
}
else
_productDescription.text =
#"Please enable In App Purchase in Settings";
}
which is successful. Then I have a button to call buyProduct:
- (IBAction)buyProduct:(id)sender {
SKPayment *payment = [SKPayment paymentWithProduct:_product];
if(_myQueue == nil) {
_myQueue = [SKPaymentQueue defaultQueue];
}
[_myQueue addPayment:payment];
NSLog(#"buyproduct happening %#", _product.localizedTitle);
}
which also successful because I see the NSLog and the product title.
But after this, I always get a transaction failed! when the purchasing process up to
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
// [self unlockFeature];
NSLog(#"Transaction purchased");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
if (_product.downloadable) {
[[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];
}
break;
case SKPaymentTransactionStateRestored:
if (transaction.downloads) {
[[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];
NSLog(#"Transaction downloading now...");
} else {
// unlock features
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(#"Transaction finish");
}
break;
case SKPaymentTransactionStateFailed:
NSLog(#"Transaction Failed");
[[SKPaymentQueue defaultQueue]
finishTransaction:transaction];
break;
default:
break;
}
}
}
What seems to be wrong and how can I debug this part? Is there a way for me to clear previous transaction test or check if there is any pending transaction interfere with the current test? I am still testing it in sandbox mode. The error was thrown in a quite immediate manner that give me the impression that it did not really go through Apple server and the transaction error happen locally, some sort of previous transaction test conflict or something that I still yet to find out.
One more point to mention is that I am purchasing non-consumable item, which if transaction successful, it will trigger the download(I hope.).

Related

IAP Fail delivering the receipt after a succes po-up

I have 2 issues :
After purchasing a subscription I receive the "You are all set Your purchase was successful" but in the SKPaymentTransactionObserver paymentQueue:updatedTransactions: I receive the SKPaymentTransactionStateFailed state with "Cannot connect to iTunes Store" error.
Some time when the purchase is initiated i receive a Confirm Purchase pop up but also the paymentQueue:updatedTransactions: is called with fail state
NOTE: I'm testing on simulator with Network Link Conditioner ( HighLatencyDNS custom profile 500ms)
Also I have notice this issue in the networks with high DNS latency more than 0.5 sec
Add Payment to queue:
- (BOOL)internalBeginPurchaseFor:(NSString *)productId
{
if ([SKPaymentQueue canMakePayments]) {
SKProduct *product;
if ((product = self.products[productId])) {
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
return YES;
}
}
return NO;
}
Processing transaction state:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
NSString *transactionIdentifier = transaction.transactionIdentifier;
NSString *transactionDetails = #"";
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
NSLog(#"User is purchasing");
transactionDetails = #"User purchasing";
break;
case SKPaymentTransactionStatePurchased:
NSLog(#"User purchased");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
[self executeHandlerForTransaction:transaction andSuccessFlag:YES];
transactionDetails = #"User purchased";
break;
case SKPaymentTransactionStateFailed:
NSLog(#"failed to purchase");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
[self executeHandlerForTransaction:transaction andSuccessFlag:NO];
transactionDetails = #"Failed to purchase";
break;
case SKPaymentTransactionStateRestored:
NSLog(#"restored transaction");
transactionDetails = #"Restored transaction";
break;
case SKPaymentTransactionStateDeferred:
NSLog(#"pending for transaction");
transactionDetails = #"Pending for transaction";
break;
}
NSString *stringData = [NSString stringWithFormat:#"%# for transaction Identifier %#",transactionDetails, transactionIdentifier];
[EventLogger logEventOfType:PURCHASE_PLAN withData:stringData andNotes:#"APIAPurchase"];
}
}
any suggestions ?

unable to finishTransaction in app purchase

I have a problem which either allows me to download apple hosted content or finishTransaction but not both;
#pragma mark -
#pragma mark SKPaymentTransactionObserver methods
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchasing:
NSLog(#"Purchasing");
break;
case SKPaymentTransactionStatePurchased:
NSLog(#"Transaction State Purchased");
[[SKPaymentQueue defaultQueue]startDownloads:transaction.downloads];
break;
case SKPaymentTransactionStateFailed:
NSLog(#"Transaction State Failed");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Transaction State Restored");
[[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];
buyButton.enabled = NO;
[buyButton setTitle:#"Purchased"
forState:UIControlStateDisabled];
//break;
default:
break;
}
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads;
{
for (SKDownload *download in downloads) {
switch (download.downloadState) {
case SKDownloadStateFinished:
NSLog(#"Completing transaction-B");
[self processDownload:download];
[queue finishTransaction:download.transaction];
[[SKPaymentQueue defaultQueue] finishTransaction:download.transaction];
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
break;
case SKDownloadStateActive:
NSLog(#"%f", download.progress);
NSLog(#"%f remaining", download.timeRemaining);
break;
default:
break;
}
}
}
-(void)completeTransaction:(SKPaymentTransaction *)transaction
{ //checked!!
NSLog(#"Complete transaction");
//[[SKPaymentQueue defaultQueue]finishTransaction:transaction];
buyButton.enabled = NO;
[buyButton setTitle:#"Purchased"
forState:UIControlStateDisabled];
}
generally the code works fine;
the product is displayed
I can select to buy
login request
then as per the code above the download works and is processed (moved to the Document directory).
However, if i finishTransaction in updatedTransactions then the download will not occur. (makes sense!)
if I call [self completeTransaction:transaction] to finishTransaction from updatedTransactions I get the same.
If i neglect to call completeTransaction (as per the code) then content is downloaded but then I can't seem to call finish transaction.
where/how do I call finish transaction?
so close but so far!!!
I was calling finish transaction too early and my download was not able to finish.

Adding more in-app purchases

I followed this tutorial to help me add in-app purchases. It works well, except now I am trying to add another non-consumable IAP. I defined my second product ID and created a method for the purchase button, but I'm not sure if I have to create another SKProductsRequest for this extra item, or if I use the same one, etc...
Here is my full code.
#define kRemoveAdsProductIdentifier #"099"
#define kDoubleEarningRateProductIdentifer #"199"
- (void)removeAds{
NSLog(#"User requests to remove ads");
if([SKPaymentQueue canMakePayments]){
NSLog(#"User can make payments");
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:kRemoveAdsProductIdentifier]];
productsRequest.delegate = self;
[productsRequest start];
}
else{
NSLog(#"User cannot make payments due to parental controls");
//this is called the user cannot make payments, most likely due to parental controls
}
}
- (void)doubleEarningRate{
NSLog(#"User requests 2x earning rate");
if([SKPaymentQueue canMakePayments]){
NSLog(#"User can make payments");
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:kDoubleEarningRateProductIdentifer]];
productsRequest.delegate = self;
[productsRequest start];
}
else{
NSLog(#"User cannot make payments due to parental controls");
//this is called the user cannot make payments, most likely due to parental controls
}
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
SKProduct *validProduct = nil;
int count = [response.products count];
if(count > 0){
validProduct = [response.products objectAtIndex:0];
NSLog(#"Products Available!");
[self purchase:validProduct];
}
else if(!validProduct){
NSLog(#"No products available");
//this is called if your product id is not valid, this shouldn't be called unless that happens.
}
}
- (void)purchase:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (void) restore{
//this is called when the user restores purchases, you should hook this up to a button
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
NSLog(#"received restored transactions: %i", queue.transactions.count);
for (SKPaymentTransaction *transaction in queue.transactions)
{
if(SKPaymentTransactionStateRestored){
NSLog(#"Transaction state -> Restored");
//called when the user successfully restores a purchase
[self doRemoveAds];
[self doubleEarningRate];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for(SKPaymentTransaction *transaction in transactions){
switch (transaction.transactionState){
case SKPaymentTransactionStatePurchasing: NSLog(#"Transaction state -> Purchasing");
//called when the user is in the process of purchasing, do not add any of your own code here.
break;
case SKPaymentTransactionStatePurchased:
//this is called when the user has successfully purchased the package (Cha-Ching!)
[self doRemoveAds]; //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(#"Transaction state -> Purchased");
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Transaction state -> Restored");
//add the same code as you did from SKPaymentTransactionStatePurchased here
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
//called when the transaction does not finnish
if(transaction.error.code != SKErrorPaymentCancelled){
NSLog(#"Transaction state -> Cancelled");
//the user cancelled the payment ;(
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
-(void)doRemoveAds
{
areAdsRemoved = true;
NSLog(#"Ads Removed");
[topBanner removeFromSuperview];
}
-(void)doDoubleEarningRate
{
}
I also read through apple documentation which explained to me what each part does, but I am still clueless on how I can add another purchase, and most other tutorials are done differently or outdated. Also all the variables and indirection is a bit intimidating to me. So I am hoping that someone can give me a quick step by step guide for adding another purchase.
To make it clearer, this approach works perfectly for just one in app purchase. However I don't know how to add more, as in I don't know how to make the program recognize which in-app purchase is being selected.
Thanks.
I fixed this by creating a method called provideContent:(NSString *)productId. Then you can either use a switch statement or if statement to differentiate between product Id's. No need to create multiple types of productRequest methods, etc...

in-app Purchase asking old account password while restoring

When I opened app it's popup's iTunes login with out any interaction of user.That popup contains my old test user account mail id sometimes my current testuser mail id aslo.I heard that i have to call
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
I called it several times where ever I thought it's needed.But still it's asking password.
And here is my code:
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for(SKPaymentTransaction *transaction in transactions)
{
NSLog(#"Updated transaction %#",transaction);
switch (transaction.transactionState)
{
case SKPaymentTransactionStateFailed:
[self errorWithTransaction:transaction];
[[SKPaymentQueue defaultQueue]finishTransaction:transaction];
break;
case SKPaymentTransactionStatePurchasing:
NSLog(#"Purchasing...");
break;
case SKPaymentTransactionStatePurchased:
{
if(no need to download)
{
// I will download the content later by restoring transactions.
[[SKPaymentQueue defaultQueue]finishTransaction:transaction];
}
else
{
[[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];
}
}
case SKPaymentTransactionStateRestored:
{
if(no need to download content)
{
[[SKPaymentQueue defaultQueue]finishTransaction:transaction];
}
else
{
[[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];
}
}
break;
default:
break;
}
}
-(void) paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads
{
for (SKDownload *download in downloads)
{
switch (download.downloadState)
{
case SKDownloadStateActive:
{
// showing download progress
}
case SKDownloadStateCancelled:
{
[[SKPaymentQueue defaultQueue] finishTransaction:download.transaction];
}
case SKDownloadStateFailed:
{
// showing alert and finish transactions
[[SKPaymentQueue defaultQueue] finishTransaction:download.transaction];
}break;
case SKDownloadStateFinished:
{
// processing content and finished the transactions
[[SKPaymentQueue defaultQueue] finishTransaction:download.transaction];
}break;
}
}
}
Is this correct and is there any place to call finishTransaction: method.Please let me know is any...
thanks in advance..

Error with adding in-app purchases - Cocos2d

I used info from this brilliant page to add in-app purchases.
However I get the NSLog No Product Available. I have checked to make sure I have added the correct Product ID. I am at a loss to what to do and have been at this for several hours.
This is for a game built on Cocos2d and I can't figure out what is wrong. Sorry for the abundance of code.
I have added <SKProductsRequestDelegate, SKPaymentTransactionObserver> and <StoreKit/StoreKit.h>
.m
#define kRemoveAdsProductIdentifier #"COINS1000"
#implementation shopCoins
- (void)buy500Coins{
NSLog(#"User requests to remove ads");
if([SKPaymentQueue canMakePayments]){
NSLog(#"User can make payments");
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:kRemoveAdsProductIdentifier]];
productsRequest.delegate = self;
[productsRequest start];
}
else{
NSLog(#"User cannot make payments due to parental controls");
//this is called the user cannot make payments, most likely due to parental controls
}
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
SKProduct *validProduct = nil;
int count = [response.products count];
if(count > 0){
validProduct = [response.products objectAtIndex:0];
NSLog(#"Products Available!");
[self purchase:validProduct];
}
else if(!validProduct){
NSLog(#"No products available");
//this is called if your product id is not valid, this shouldn't be called unless that happens.
}
}
- (IBAction)purchase:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (IBAction) restore{
//this is called when the user restores purchases, you should hook this up to a button
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
NSLog(#"received restored transactions: %i", queue.transactions.count);
for (SKPaymentTransaction *transaction in queue.transactions)
{
if(SKPaymentTransactionStateRestored){
NSLog(#"Transaction state -> Restored");
//called when the user successfully restores a purchase
[self doRemoveAds];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for(SKPaymentTransaction *transaction in transactions){
switch (transaction.transactionState){
case SKPaymentTransactionStatePurchasing: NSLog(#"Transaction state -> Purchasing");
//called when the user is in the process of purchasing, do not add any of your own code here.
break;
case SKPaymentTransactionStatePurchased:
//this is called when the user has successfully purchased the package (Cha-Ching!)
[self doRemoveAds]; //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(#"Transaction state -> Purchased");
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Transaction state -> Restored");
//add the same code as you did from SKPaymentTransactionStatePurchased here
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
//called when the transaction does not finnish
if(transaction.error.code != SKErrorPaymentCancelled){
NSLog(#"Transaction state -> Cancelled");
//the user cancelled the payment ;(
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)doRemoveAds{
NSLog(#"Bitches");
}
If it's a debug build you probably need to add a sandbox user on iTunes connect this will allow you to use IAP that have not been reviewed yet. Note this user should match your iTunes connect account.

Resources