In-app purchase, how to provide consumables after purchase - iphone

I am trying to implement IAPs in one app and I'm almost there but still facing some problems. I already posted another question before of this because I found an incomplete tutorial that was missing something so I was stuck and someone already helped me out with that... but in that tutorial they were using a table while I just want to use normal buttons. Between various answers there was an answer that gave me a new way to approach this matter.. so I am giving it a try but here comes the problems I found:
I am getting a warning, the guy told me in the answer to include each of the following protocols in my header file:
SKProductsRequestDelegate
SKPaymentTransactionObserver
SKRequestDelegate
and I did it with this code:
#interface BuyTest2 : UIViewController <SKProductsRequestDelegate, SKPaymentTransactionObserver, SKRequestDelegate>
but now I'm getting this warning in m file:
Method 'paymentQueue:updatedTransactions:' in protocol not implemented
why is that? Am I missing something? (I'm sure I am...)
when I click button to buy Coins everything works fine but after the purchase has been done I don't know how to deliver the coins... I have my code to do it but I don't know where to put it... how should I do that?
if I try again to make a test purchase I get the message that I already bought that item and that I have to click on 'OK' to download it... but after that nothing happens... and I really don't have to give something to download but I just need to add some coins to a variable and then save it with NSUserDefaults...
here is the code I am using:
in file .h i got
#interface BuyTest2 : UIViewController <SKProductsRequestDelegate, SKPaymentTransactionObserver, SKRequestDelegate>
#property (nonatomic, retain) SKProduct *currentProduct;
#property(nonatomic, readonly) SKPaymentTransactionState transactionState;
#property (nonatomic, retain) SKProductsRequest *ualRequest;
and in file .m i got:
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
if(response.products.count > 0)
{
SKProduct* product;
for(int i = 0; i<response.products.count; i++)
{
product = [response.products objectAtIndex:i];
if([product.productIdentifier isEqualToString:#"com.mycompany.myapp.1"])
{
self.currentProduct = product;
[self beginPaymentWithProduct:product];
}
}
}
}
- (void)beginPaymentWithProduct:(SKProduct*)product
{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (BOOL)canMakePurchases
{
return [SKPaymentQueue canMakePayments];
}
- (IBAction)buyCoins:(id)sender
{
if([self canMakePurchases])
{
ualRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:[NSArray arrayWithObjects: #"com.mycompany.myapp.1", nil]]];
[ualRequest setDelegate:self];
[ualRequest start];
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction* transaction in transactions) {
if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
NSLog(#"Transaction Purchased: %#", transaction);
// Make purchase available to the user, etc...
// Once that's all done...
[queue finishTransaction:transaction];
}
else if (transaction.transactionState == SKPaymentTransactionStateFailed) {
NSLog(#"Transaction Failed: %#", transaction);
// Display error to the user, using the error text in the transaction
// This example uses NSLog, typically you'd use UIAlertView here
NSLog(#"Error: %#", [transaction.error localizedDescription]);
}
}
}
Thanks for any help...

but now I'm getting this warning in m file:
Method 'paymentQueue:updatedTransactions:' in protocol not implemented
The error message is very informative here. paymentQueue:updatedTransactions: is part of the SKPaymentTransactionObserver protocol, and is a required method (meaning, you have to implement it if you are to become an observer of payment transactions. Remember, warnings are something you should take a serious look at, and typically you should consider them as errors). The documentation for this protocol can be found here.
The discussion for this particular method is fairly clear on it's purpose, and what you are required to do. It is the means to check whether a transaction has gone though, and if it has it is your responsibility to provide the functionality/content to the user.
The application should process each transaction by examining the transaction’s transactionState property. If transactionState is SKPaymentTransactionStatePurchased, payment was successfully received for the desired functionality. The application should make the functionality available to the user. If transactionState is SKPaymentTransactionStateFailed, the application can read the transaction’s error property to return a meaningful error to the user.
Once a transaction is processed, it should be removed from the payment queue by calling the payment queue’s finishTransaction: method, passing the transaction as a parameter.
The best documentation on how to use the IAP system is available from Apple, and I strongly recommend you give it a good read. It is available here.
Update:
Here is a code snippet showing the general gist of what's required in this method:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction* transaction in transactions) {
if (transaction.transationState == SKPaymentTransactionStatePurchased) {
NSLog(#"Transaction Purchased: %#", transaction);
// Make purchase available to the user, etc...
// Once that's all done...
[queue finishTransaction:transaction];
}
else if (transaction.transactionState == SKPaymentTransactionStateFailed) {
NSLog(#"Transaction Failed: %#", transaction);
// Display error to the user, using the error text in the transaction
// This example uses NSLog, typically you'd use UIAlertView here
NSLog(#"Error: %#", [transaction.error localizedDescription]);
}
}
}
And remember to register yourself for events. You can do this via:
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
Add these in the appropriate places, such as init/dealloc. Do refrain from using this in viewDidLoad, as this is more of a visual thing, and not a data model concern.

Related

XMPPFramework on iOS -xmppRoomDidDestroy: not getting called

I'm developing an iPhone app using the XMPP framework for iOS/OSX and I'm using the XEP-0045 extension (multi-user chat). I've got the room creating and configuring successfully and I am able to invite other users and chat with them. The problem arises when I go to destroy the room I've created. I've followed the code-path that gets executed inside the framework and I've figured out why the framework isn't firing the method, but I'm not sure how it would ever fire the method given the behavior I'm seeing.
That behavior is as follows:
1) I request that the room be destroyed by calling [room destroyRoom]
2) I then see that the XMPPRoom class sets up its XMPPIDResponse tracker to watch for the "result" iq stanza that the server will send back saying it's successfully deleted the room.
3) (Here is where the problem arises) I receive a presence stanza from the room saying it's now unavailable (since I, too, am an occupant of the room) and the framework then clears the response tracker and calls -xmppRoomDidLeave:.
4) The server then sends back the "result" iq stanza saying that the room was successfully deleted, but no one is listening anymore. This results in the call to xmppRoomDidDestroy being missed.
This behavior is consistent with what I've read in the XEP-0045 definition and, given that, I'm not sure how the -xmppRoomDidDestroy: delegate would ever get called. Am I doing something wrong here?
I had the same problem and this is my solution:
In XMPPRoom method
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
replace
[responseTracker removeAllIDs];
with:
if ([x elementForName:#"destroy"]) {
NSArray *allKeys = [responseTracker allIDs];
for (NSString *key in allKeys) {
SEL aSel = [responseTracker selectorForElementID:key];
if (aSel != #selector(handleDestroyRoomResponse:withInfo:)) {
[responseTracker removeID:key];
}
}
} else {
[responseTracker removeAllIDs];
}
In XMPPIDTracker.h add declaration for new public methods
- (NSArray *)allIDs;
- (SEL)selectorForElementID:(NSString *)elementID;
Also in XMPPBasicTrackingInfo interface add
#property (nonatomic, readonly) SEL selector;
In XMPPIDTracker.m add two public methods
- (NSArray *)allIDs {
return [dict allKeys];
}
- (SEL)selectorForElementID:(NSString *)elementID {
id <XMPPTrackingInfo> info = [dict objectForKey:elementID];
if ([info isKindOfClass:[XMPPBasicTrackingInfo class]]) {
return [(XMPPBasicTrackingInfo *)info selector];
}
return nil;
}
At the end in the #implementation XMPPBasicTrackingInfo add
#synthesize selector;
Basicaly this code removes all response trackers except handleDestroyRoomResponse:withInfo: because we need this hander to respond on IQ destroy stanza.
I hope this will help.
Same issue with lastest XMPPFramework 3.6.6.
Here is the solution:
Still use destroyRoom function, xmppStream:didReceivePresence: and xmppRoomDidLeave: will be called, test by add NSLog inside both delegate, then i use Spark(Windows version) to destory a room, the Xcode console print the following text:
<presence xmlns="jabber:client" type="unavailable" from="wei#conference.10.50.200.94/admin" to="admin#10.50.200.94/iOS">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" role="none"></item>
<destroy>
<reason>destroy room</reason>
</destroy>
</x>
</presence>
I use the <destroy> element to determine the destroyRoom action.
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
{
/*
<presence xmlns="jabber:client" type="unavailable" from="wei#conference.10.50.200.94/admin" to="admin#10.50.200.94/iOS"><x xmlns="http://jabber.org/protocol/muc#user"><item affiliation="none" role="none"></item><destroy><reason>destroy room</reason></destroy></x></presence>
*/
// NSLog(#"%s %#", __FUNCTION__, presence);
if ([presence.fromStr containsString:#"conference"]) {
NSXMLElement *x = [presence elementForName:#"x"];
if (x) {
NSXMLElement *destroy = [x elementForName:#"destroy"];
if (destroy) {
XMPPJID *roomJID = presence.from.bareJID;
// TODO: do something with the roomJID
}
}
}
}
But XMPPFramework send a empty <destory/> element, i guess the openfire server simply ignore the <destroy/> and response <presence>..</presence> without <destroy>.
Modify the destroyRoom function in XMPPFramework or implement the following delegate to add a destroy reason.
- (XMPPIQ *)xmppStream:(XMPPStream *)sender willSendIQ:(XMPPIQ *)iq
{
/*
<iq type="set" to="weixin#conference.10.50.200.94" id="BCF55C6A-9C5E-4740-BE6A-63E17B5C58F6"><query xmlns="http://jabber.org/protocol/muc#owner"><destroy></destroy></query></iq>
*/
if ([iq isSetIQ]) {
NSXMLElement *query = [iq elementForName:#"query" xmlns:XMPPMUCOwnerNamespace];
if (query) {
NSXMLElement *destroy = [query elementForName:#"destroy"];
if (destroy) {
NSXMLElement *reason = [destroy elementForName:#"reason"];
if (!reason) {
reason = [NSXMLElement elementWithName:#"reason" stringValue:#"destroy reason"];
[destroy addChild:reason];
}
}
}
}
return iq;
}

What to do if Apple hosted in-app purchase content download fails?

Sometimes the download fails and my app finishes the transaction upon failure, as recommended everywhere. But if I finish the transaction then Store Kit fails to resume the failed download. And it also seems Store Kit doesn't automatically retry to download content once download failed, and there also seems to be no way to trigger download.
Has someone figured out what to do in such cases? Go against all recommendation and not finish the transaction until the content has fully been downloaded and installed?
Or must I programmatically restore transactions and ignore all other product identifiers?
You can add a button of restore purchase via this way in your app :-
//inside of an IBaction
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue]restoreCompletedTransactions];
// Then this is called
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
NSLog(#"%#",queue );
NSLog(#"Restored Transactions are once again in Queue for purchasing %#",[queue transactions]);
NSMutableArray *purchasedItemIDs = [[NSMutableArray alloc] init];
NSLog(#"received restored transactions: %i", queue.transactions.count);
for (SKPaymentTransaction *transaction in queue.transactions) {
NSString *productID = transaction.payment.productIdentifier;
[purchasedItemIDs addObject:productID];
NSLog (#"product id is %#" , productID);
// here put an if/then statement to write files based on previously purchased items
// example if ([productID isEqualToString: #"youruniqueproductidentifier]){write files} else { nslog sorry}
}
}
OR
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
Hope this help you...!!

IAP for IOS not asking me to purchase the item and gives me "no product"

I am trying to implement IAP for my new version of my app in the app store.
here are everything I did :
made new version in itunes connect and the Status = Prepare for Upload.
add new products in "Manage in-app purchase"
create test user account.
implement the flowing code :
-(IBAction)audio{
//test the restriction
if ([SKPaymentQueue canMakePayments]){
SKProductsRequest *skproductrequiste = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:#"AudioPlayer"]];
skproductrequiste.delegate = self;
[skproductrequiste start];
}
//after purshsing youcan show
/*
detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"show_pro"];
detailViewController.product = #"audio";
[self.navigationController pushViewController:detailViewController animated:YES];
*/
}
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
SKProduct *validproducte = nil;
int count = [response.products count];
if (count >0) {
validproducte = [response.products objectAtIndex:0];
SKPayment *pay = [SKPayment paymentWithProductIdentifier:#"AudioPlayer"];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue]addPayment:pay];
}
else{NSLog(#"no product");}
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for(SKPaymentTransaction *transiction in transactions){
switch (transiction.transactionState) {
case SKPaymentTransactionStatePurchasing:
break;
case SKPaymentTransactionStatePurchased:
NSLog(#"dobe buying");
[[SKPaymentQueue defaultQueue]finishTransaction:transiction];
break;
case SKPaymentTransactionStateRestored:
break;
case SKPaymentTransactionStateFailed:
break;
}
}
}
before I test my app in real device , I logged out of app store.then run the app and pressed the audio IBAction, but it gives me "no product". it did not even ask me to log in app store.
what is the problem here ? also , I am wondering how can I save customers purchasing in case if they deleted the app and reinstall again ?
thanks in advance .
IAP doesn't work on the simulator if you're trying it there.
A request for a list of products doesn't require a login.
Make sure your product ID is the EXACT same as what you entered in the Manage In App Purchase. It will return "0" if it doesn't match.
Sometimes the IAP "sandbox" is down and returns no items. Check the IAP section of the Apple iOS developer forums to see if anybody else is seeing this issue.
Have you updated your Contracts, Tax, and Banking information in your account. without that you will not get your product info.
Thank you

Obj-C, Storekit restoreCompletedTransactions returns zero transactions?

I'm having some problems restoring completed transactions.
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
I've added the observer mentioned in several examples, I've tried adding paymentQueueRestoreCompletedTransactionsFinished and already have updatedTransactions. paymentQueueRestoreCompletedTransactionsFinished says I have zero transactions.
I can buy a product and if I try to buy again, it stops me and says I've already bought the product, using this code.
SKPayment *payment = [SKPayment paymentWithProductIdentifier:productIdentifier];
[[SKPaymentQueue defaultQueue] addPayment:payment];
I thought maybe I had a problem with my bundle identifier, but that seems fine and the buy wouldn't work if it wasn't.
I have been trying this on the device as well as the simulator, but this has the same result. Also, it doesn't make a difference If I'm using UK or US store.
I'm really grasping at straws to find out why this doesn't work for me ?
try to do it like this and check the array count is it return zero also ?
- (void) checkPurchasedItems
{
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}//You Call This Function
//Then this delegate Function Will be fired
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
purchasedItemIDs = [[NSMutableArray alloc] init];
NSLog(#"received restored transactions: %i", queue.transactions.count);
for (SKPaymentTransaction *transaction in queue.transactions)
{
NSString *productID = transaction.payment.productIdentifier;
[purchasedItemIDs addObject:productID];
}
}
According to the docs:
When you create a new product to be sold in your store, you choose whether that product can be restored or not.
So the question is, is your product configured to allow restores?

Turning off an in-app purchase?

We currently have a live app that features episodic content in the App store.
We're re-working our pricing, and instead of offering individual episodes for purchase, we want to have it as simply an entire pack of episodes.
My question is this: If I set my old identifiers to NOT cleared for sale, would a user who previously purchased that content still be allowed access to it? (Meaning if I query whether they've purchased it, will it return true)
I'm new to the in-app purchase side of apps, and I'm not entirely sure how that works.
Also, if I delete the identifier from iTunesConnect, what happens? Should this even be done?
Thanks in advance for any insight
When using [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];, Apple will return all completed transactions in a SKPaymentQueue which is a collection of transactions. The transaction will contain the the payment object. The payment object will contain the productIdentifier. This info is available despite your deletion. Thus you may honor past purchases that are no longer for purchase.
Here is some example code:
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
#pragma mark SKPayment Observer Delegate methods
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
NSLog(#"received restored transactions: %i", queue.transactions.count);
for (SKPaymentTransaction *transaction in queue.transactions)
{
NSLog(#"tran for product: %# of state: %i", [[transaction payment] productIdentifier], [transaction transactionState]);
switch ([transaction transactionState])
{
case SKPaymentTransactionStateRestored:
NSLog(#"found restored transaction: %# productIdentifier: %#", transaction.transactionIdentifier, transaction.payment.productIdentifier);
[self yourRestoreProcessSelector:transaction];
break;
default:
break;
}
}
}