Im doing a in app purchase non consumable,the issue is when I start the app there comes a alert view filled with a mail address already and asking password.This particular issue happens only in the same device which I test where else it never happened when I tested in other device.
Somewhere I have to place this [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; to get that unfinished transaction done but I'm completely confused because I've placed in necessary places .This is my existing code
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
[self UnlockPurchase];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Restored ");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:NSLog(#"Transaction Failed");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
default:
break;
}
}
- (IBAction)Restore:(id)sender {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
[self UnlockPurchase];
}
- (IBAction)BuyProduct:(id)sender {
SKPayment *payment = [SKPayment paymentWithProduct:_product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
Similar problem I had previously on another device just because I left out [[SKPaymentQueue defaultQueue] finishTransaction:transaction]in SKPaymentTransactionStateRestoredthen my problem was solved.When I used this test user to restore the purchase,it restores without entering into SKPaymentTransactionStateRestored case.When I try to purchase again it says you've already purchased item but hasn't been downloaded.Now I need is to get rid of that login containing a email address filled already pops up when I start? HELP !
You are missing the methods that handle the response of the app store to your application. They should be in your app delegate:
Follow apples documentation for delivering products:
https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/DeliverProduct.html#//apple_ref/doc/uid/TP40008267-CH5-SW3
Alternatively you can use ray wenderlichs tutorial:
http://www.raywenderlich.com/21081/
Take notice that these methods are the ones that should control the content delivery, for the purchase being successful or not.
Related
I have made a demo of IAP and in that I have registered my demo App in the apple account, I also have added the products info , description, app-id, title, priceTier, sandbox account for testing the payment process as described in the demo. I am facing two problems right now:-
a) Like for the very first time the popUp will ask for apple-id/password and I have provided both the values. It will show whether I want to confirm my payment and in that popup it will display the product name and price of the product. After clicking OK I am just getting nothing not even a mail from Apple, so how can I justify whether I have done a successful payment.
b) I also want to change my login id while buying the product. But every time it is by default taking my previous id and show me the password box only to enter the password. How can I reset this settings? Like I have tried reseting the simulator and again adding a new product in the my apple a/c in iTunes. So if any one got any idea please free to help. sharing my code below:-
-(void)fetchAvailableProducts{
if ([SKPaymentQueue canMakePayments])
{
SKProductsRequest *request = [[SKProductsRequest alloc]
initWithProductIdentifiers:
[NSSet setWithObject:kTutorialPointProductID]];
request.delegate = self;
[request start];
}
else
NSLog(#"Please enable In App Purchase in Settings") ;
}
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray *products = response.products;
if (products.count != 0)
{
_product = products[0];
NSLog(#"procudt info is %#",_product.localizedTitle);
// _buyButton.enabled = YES;
// _productTitle.text = _product.localizedTitle;
// _productDescription.text = _product.localizedDescription;
[self purchaseProducts];
} else {
//_productTitle.text = #"Product not found";
}
products = response.invalidProductIdentifiers;
for (SKProduct *product in products)
{
NSLog(#"Product not found: %#", product);
}
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
[[SKPaymentQueue defaultQueue]
finishTransaction:transaction];
NSLog(#"Transaction complete");
break;
case SKPaymentTransactionStateFailed:
NSLog(#"Transaction Failed");
[[SKPaymentQueue defaultQueue]
finishTransaction:transaction];
break;
default:
break;
}
}
}
First of all I am just fetching my product using my product id and I am getting response in the delegate method and if count >0 then "purchaseProducts" method will be called. But the thing is that my delegate method is not getting called, nothing is happening after I click on "OK" to buy the product. Please share your ideas.
Hi Follow the given steps, hope it works-
First check if the user can make payments or not-
-(void)checkForPayement{
//First check if user can make payments
if (![SKPaymentQueue canMakePayments]) {
// Show the error that user can't make payments.
}else{
// Make the product request on App Store
SKProductsRequest *productRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:IAP_PRODUCT_ID]]; // IAP_PRODUCT_ID is the product id which you got when you register the in-app-purchase
productRequest.delegate = self;
[productRequest start];
}
}
If the product is available the you will get response in the following method-
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
if (response.products.count > 0) {
SKProduct *validProduct = [response.products objectAtIndex:0];
[self purchase:validProduct];
} else {
// Product is not available so please cross check about the IAP_PRODUCT_ID
}
}
If the request is failed, you will get callback in following method-
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error{
// Check here for the error
}
Start your purchase
- (void)purchase:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
You will get callback in given method and you can check the status of the transaction-
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
DDLogVerbose(#"Purchasing");
break;
case SKPaymentTransactionStateFailed:
if (transaction.error.code == SKErrorPaymentCancelled) {
// User has cancelled the transaction
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
DDLogVerbose(#"Restored");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStatePurchased:
{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
// Payment has been done and you should provide the purchased content or you may verify the receipt also
DDLogVerbose(#"Purchased");
}
break;
default:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
If you want more security then you can also Verify receipt from app store
Solution of your problems
a. While testing the in-app-purchase in sandbox environment, you will not get any mail for payment confirmation, you can check status of your payment in Storekit updatedTransactions delegate method. And no balance will be deducted in Sandbox environment.After entering the sandbox account details, also check the iTunes account if you can login or not? If not, you id may not verified so verify your id.
b. Before trying in-app-purchase just logout the apple id from Settings -> iTunes & App Store. Tap on the apple id and click on logout. And when you initiate in-app-purchase enter the sandbox account credentials and don't login the sandbox account in actual production environment because it may invalidate your sandbox account.
In my app In-App Purchases are working fine.
The issue I am facing is that;
if I initiate a subscription process and send app to background by pressing Home button on iPhone.
then In-App purchases API keeps on working and prompts user for iTunes credentials, while app is in background and user completes the process of purchasing the subscription successfully.
Now user has purchased the subscription through In-App API while app is in background but I am not getting that how to handle this scenario as, if user kills app without taking it back to foreground then the subscription purchase information will never be forwarded to our server and we will not be able to update user account in our server and user will not be able to use the special features of the app.
To send the latest receipt and In-App Purchase info to server, I get notified through below method:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
NSLog(#"updated transaction");
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
NSLog(#"transationStatePurchased");
// here I send data to server, but it never runs if app is in background.
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(#"transationStateFailed");
[self failedTransaction:transaction];
break;
default:
break;
}
}
}
But the above method always works when app is in foreground so the issue is how can I get notified about the complete transactions even if above method does not executes.
OK, So I have got the answer.
For others in easy words.
If you have an interrupted In-App Purchase which was due to any reason was completed but the receipt/information of that transaction of In-App Purchase, could not be sent to your server. Simply do this
Most Important: NEVER CALL [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; BEFORE SAVING THE RECEIPT ON YOUR SERVER, CALL IT WHEN PROCESS OF SAVING THE RECEIPT COMPLETES SUCCESSFULLY, SO THAT IF TRANSACTION GETS INTERRUPTED YOU WOULD HAVE INFORMATION REGARDING IT, NEXT TIME WHEN USER WILL START THE APP.
In your app delegate or in your first view controller or wherever you want, add this: (In my scenario I need to update user data before login so that user could use the special features of the app)
Add #import <StoreKit/StoreKit.h> to your .h file.
Add Delegate <SKPaymentTransactionObserver> to .h file.
Add observer
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
to - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
And delegate Method
-(void) paymentQueue:(SKPaymentQueue *) queue updatedTransactions:(NSArray *) transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
NSLog(#"Purchasing");
break;
case SKPaymentTransactionStatePurchased:
if ([transaction.payment.productIdentifier isEqualToString:kProductID]) {
NSLog(#"Purchased ");
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:
#"Purchase is completed succesfully" message:nil delegate:
self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alertView show];
}
[self sendReceiptDataToServer:transaction];
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Restored ");
[self methodAfterTransactionSuccessful:transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(#"Purchase failed ");
break;
default:
break;
}
}}
-(void) sendReceiptDataToServer:(SKPaymentTransaction*)transaction {
NSLog(#"transaction successful");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
I'm testing out my In App Purchases in sandbox mode and I keep getting the message "This in-app purchaes has already been bought" when I try to purchase it, wherein the product is supposed to be consumable that can be bought unlimited times. I tried kiling my app and restarting it, but it continues to give me this message. Is this an Xcode Objective C bug, or is this an iOS bug? I'd be most grateful for any answers or guidance in resolving this matter, thanks in advance.
Update: Code for SKPayment
- (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:validProduct];
//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");
[self doRemoveAds:validProduct];
//add the same code as you did from SKPaymentTransactionStatePurchased here
break;
case SKPaymentTransactionStateFailed:
//called when the transaction does not finish
if(transaction.error.code == SKErrorPaymentCancelled){
NSLog(#"Transaction state -> Cancelled");
//the user cancelled the payment ;
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
Firs time i tried to make a subscription this function call for all transactions, but after then if i tried to subscribe it calls just 1 state.
This code same as in apple developer documentation
- (void)paymentQueue:(SKPaymentQueue *)queue
updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
NSLog(#"purchasing in progress");
break;
case SKPaymentTransactionStateDeferred:
NSLog(#"Stade deferred");
break;
case SKPaymentTransactionStateFailed:
NSLog(#"State failed");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStatePurchased:
NSLog(#"Sucsess purchased");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
NSLog(#"restored");
break;
default:
// For debugging
NSLog(#"Unexpected transaction state %#",#(transaction.transactionState));
break;
}
}
}
updatedTransactions is called only for updated transactions (see Apple docs https://developer.apple.com/library/prerelease/ios/documentation/StoreKit/Reference/SKPaymentTransactionObserver_Protocol/)
transactions - An array of the transactions that were updated.
But on application start or first purchase iOS sends every transaction from SKPaymentQueue: defaultQueue.
https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/DeliverProduct.html:
Terminate and relaunch your app. Store Kit calls the paymentQueue:updatedTransactions: method again shortly after launch; this time, let your app respond normally. Verify that your app correctly delivers the product and completes the transaction
So if app should view all transactions, it should request SKPaymentQueue: defaultQueue.
I'm implementing In-App purchase with my server delivering the purchases in the form of zip files (not Apple server).
As downloadable files are quite big, 50Mb-500Mb I would like to let the user buy the items
and only when they want download and install the purchases.
So my implementation would be
By - Close Apple Transaction and Mark Item as Purchased - Trigger Download when you Want
Apple docs suggest you close transaction only after download completed:
I've been reading all Apple docs and I cannot see any specific comment about this saying that you must
close transaction only after download.
Am I going toward an Apple rejection?
Here the code: in case "SKPaymentTransactionStatePurchased" I call the method
"completeTransaction" which is closing the transaction itself. My question is about the
method:
[self provideContent:transaction.payment.productIdentifier];
Can I move this download from here in later action ofter "finishTransaction"?
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
// [self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
// [self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
// [self restoreTransaction:transaction];
default:
break;
}
}
}
- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
[self recordTransaction:transaction];
[self provideContent:transaction.payment.productIdentifier];
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}