I am implementing inApp features with download hosted content feature. All products are free for unlimited time. Each product have one download associated and that is audio file.
Is possible to start download without user see dialog to buy free product. First user must give his apple id password, than he see dialog to buy free product and than dialog that product is succesfully purchased.Than download can be requested.
But since all products are free can somehow download be reuested without user see confiramtion dialogs and messages and start download imedietly.
If item was previously be purchased/downloaded than user see message that will buy free item again. I know for restore functionalities, but do I need to track previously downloaded items and in that case start download from restore state.
Thanks!
1) Yes, You can download without dialogs. get product detail using store kit, and if product price is free (0), download item without processing purchase request from Storekit.
Code to request product detail:
KProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithArray:productIdentifiers]];
// Keep a strong reference to the request.
self.request = productsRequest;
productsRequest.delegate = self;
[productsRequest start];
// SKProductsRequestDelegate protocol method
- (void)productsRequest:(SKProductsRequest *)request
didReceiveResponse:(SKProductsResponse *)response
{
self.products = response.products;
for (NSString *invalidIdentifier in response.invalidProductIdentifiers) {
// Handle any invalid product identifiers. or check that product you are looking for dozens has its identifier here
}
for (SKProduct *product in self.products)
{
//for example, product you are looking for has identifier "com.product.free"
if([product.productIdentifier isEqualToString:#"com.product.free"] && [product.price compareWithInt:0])
{
// Startdownloading without purchase
}
}
}
2) If you have non-consumable inappt purchase, you must provide option for restore. for downloading, you can write your own logic. check if item is downloaded and re-download only if its not available.
Related
I am trying to enable In-App Purchases in Swift 4 for iOS and I can not get the Display Name and Description to properly show up (or load) in the app when I test. I do get the error handing message that I put in if (products.count == 0) {. I can't get the Product Title and Product Description that I set up in AppStoreConnect to properly load when I test my app. Instead the code thinks that there are no products because it runs the code in the if (products.count == 0) { closure. I quadruple checked everything else (i.e. Bundle ID, Product ID, StoreKit's imported, the delegates are set up, everything on the AppStoreConnect side is set up, etc.). There are no Xcode Warnings and the In-App Purchase status is "Ready To Submit". Any suggestions?
var product: SKProduct?
var productID = "myProductID"
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
var products = response.products
if (products.count == 0) {
productTitle.text = "Warning" // GETS DISPLAYED
productDescripton.text = "Unable to connect to In-App Purchase." // GETS DISPLAYED
} else {
product = products[0]
productTitle.text = product?.localizedTitle
productDescripton.text = product?.localizedDescription
buyButton.isEnabled = true
buyButton.alpha = 1.0
}
let invalid = response.invalidProductIdentifiers
for product in invalid {
print("\(product)")
}
}
If you are a rookie at integrating In-App Purchases, then you should read this. If you have clean code and you think that you set everything up properly in App Store Connect, but still can't test properly. Specifically, if your products array is showing empty when it shouldn't be. Then, make sure that you have done the following:
1) Set up a Sandbox Tester via Users and Access in App Store Connect. This needs to be set up with an email that is not already used with iTunes, App Store, or Apple.
2) Filled out all of the necessary forms in Agreements, Tax, and Banking via App Store Connect. You'll know that everything is filled out when Paid Applications shows an Active Status.
3) Are testing on a real device that has been signed out of iTunes and App Store.
Common things that will cause this without warning are:
Your products aren't in the 'Ready to Submit' stage. This is most often from a missing screenshot, which is required even for Sandbox (you can upload a blank image for sandbox testing).
You haven't signed the 'Paid Applications' agreement in App Store Connect
Here's a good blog post that covers everything in a little more detail: Configuring In-app Products is Hard
I'm doing the In-App purchase feature. Today I get a weird issue.
I try to get list products by SKProductsRequest. The problem is: sometimes I received invalidProductIdentifiers, but sometimes I received valid products.
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObjects:objects]];
productsRequest.delegate = self;
[productsRequest start];
Ex: I send a request to get info of 30 products: sometimes get 10 products are valid, sometimes 0, sometimes 30, ... So weird.
In the past, it works well. But one day the problem occurs.
I tried (searched and asked my co-workers) a day but still can not figure out why.
Please help.
Thank you.
First of all check your application id should not be with wild card (*), it cannot be enabled for In-App Purchases so make sure you should create a new App ID.
If above scenario is not your case, then you will have to wait for maximum 24 hours after creating new product id from your developer account, within this time your product id will be fetched from your application. Because some times apple takes time to enabled product ids.
If above both are not your cases then at last you will have to check your product id’s are valid or invalid with below code.
- (void)productsRequest:(SKProductsRequest )request didReceiveResponse:(SKProductsResponse )response {
for (NSString *invalidProductId in response.invalidProductIdentifiers)
{
NSLog(#"Invalid product id: %#" , invalidProductId);
}
}
If you found your product ids are invalid please go through with http://troybrant.net/blog/2010/01/invalid-product-ids/
Cheers.....
Thank you all.
"But one day the problem occurs". And one day, it disappeared.
I think it's the Apple's bug.
If I wipe the data in my app, then re-purchase some managed IAP that I know the account already owns, iOS gives me the native "are you sure you wish to repurchase this item you will not be charged" dialog. That's as expected.
When the purchase returns to my app, I get the receipt with [[NSBundle mainBundle] appStoreReceiptURL]. I then attempt to verify the SKPaymentTransaction's transactionIdentifier with the receipt via my server.
However the receipt's transaction ID for this IAP, because I've already purchased it long ago, is not the same as the SKPaymentTransaction.
How should I be verifying that this is a valid repurchase? Can I get a signed receipt for this repurchase somehow?
All the purchases will be in a single receipt. You do have to check all iAPs there and look for the needed ones.
If you want the id of the original iAP transaction, there is Original Transaction Identifier
For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier.
More information about receipt fields is here: https://developer.apple.com/library/prerelease/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
Anyway, does it really matter whether this is an original purchase or a repurchase? My checks of this are usually the same.
As far as I understand it you should check the originalTransaction to check that this is valid for transactions that are SKPaymentTransactionStateRestored.
[[NSBundle mainBundle] appStoreReceiptURL] is for the receipt of the main application, not the IAP.
You can use RMStore library, and check all purchases:
RMStoreAppReceiptVerificator *verificator = [[RMStoreAppReceiptVerificator alloc] init];
if ([verificator verifyAppReceipt])
{
NSArray *inAppPurchases = [RMAppReceipt bundleReceipt].inAppPurchases;
for (RMAppReceiptIAP *inAppPurchase in inAppPurchases)
{
NSLog(#"productIdentifier %# originalPurchaseDate %#", inAppPurchase.productIdentifier, inAppPurchase.originalPurchaseDate);
}
}
If your receipt is nil you can refresh receipt:
[[RMStore defaultStore] refreshReceiptOnSuccess:^{
//get purchases
} failure:^(NSError *error) {
}];
I have a problem with validating an apple receipt on the server-side.
I tried to find a solution in the internet, but haven't succeeded.
So, description:
First of all, application is made for iOS7. Secondly, I have a few items ( type = Non-Renewing Subscription ). So user can buy one or multiple items and then he should manually renew them ( buy again ).
Applications sends a receipt to the server-side, I make a request to the Apple and get the result with a lot of in_app receipts. Something like:
"in_app":[
{
"quantity":"1", "product_id":"...", "transaction_id":"...",
"original_transaction_id":"...", "purchase_date":"...",
"purchase_date_ms":"...", "purchase_date_pst":"...",
"original_purchase_date":"...",
"original_purchase_date_ms":"...", "original_purchase_date_pst":"...",
"is_trial_period":"..."},
{
"quantity":"1", "product_id":"...",
"transaction_id":"...","original_transaction_id":"...",
"purchase_date":"...", "purchase_date_ms":"...",
"purchase_date_pst":"...", "original_purchase_date":"...",
"original_purchase_date_ms":"...", "original_purchase_date_pst":"...",
"is_trial_period":"..."}
]
So, each "receipt" in "in_app" has transaction_id. But how I can identify the transactionId of the current purchase? I would like to validate it as well and make sure that this is unique.
My concern is: if somebody will get one valid receipt, he will be able to hack our server-side API and make unlimited numbers of in-app purchases with the same valid receipt.
Should I somehow decrypt and check for transaction_id the "original" receipt, the one what I send to Apple for verification?
Any help/suggestions would be highly appreciated.
Thank you in advance.
Regards,
Maksim
#Doug Smith
https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
If you go through the different fields on this page, you will find
Original Transaction Identifier::
For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier.
This value corresponds to the original transaction’s transactionIdentifier property.
All receipts in a chain of renewals for an auto-renewable subscription have the same value for this field.
So for your non-auto renewable subscriptions, you have to keep track of two things on your server side:
The original transaction identifier of the receipt that you are validating with itunes server, associate this with the user Id in your database.
Whether the request that you received from the client side is of a Purchase or of a Restore Purchase.
Once you have these two things with you, you can write your logic on these two parameters like below:
::If a request is of type "Purchase" and you already have the original transaction identifier of that receipt associated with some other user Id, you can block that purchase.
::If a request is of type "Restore Purchase" and request is coming from the same user id against which the original transaction identifier is associated in your DB than allow him otherwise block his restore.
Furthermore, you can derive your own logic based on these things, according to your needs.
Let me know if you have any further doubts.
For each new transaction apple send a new receipt which is unique, encode it so no one can forge data.
Get the transaction receipt from the completed transaction encode it and send it to your server, and on the server side decode it and match with the one apple send to server.
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
_transactionArray = transactions;
for (SKPaymentTransaction * transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased: {
NSData *receipt = transaction.transactionReceipt;
[self sendReceiptToServer];
} break;
case SKPaymentTransactionStateFailed: {
// finish this transaction
} break;
case SKPaymentTransactionStateRestored:
NSData *receipt = transaction.transactionReceipt;
[self sendReceiptToServer:receipt];
} break;
default:
break;
}
};
}
-(void)sendReceiptToServer:(NSData *)receipt {
// encode receipt
// send receipt to server
// add success and error callback
}
-(void) receiptSuccess {
// finish transaction here
}
-(void) receiptError {
// try again sending receipt to server
}
I am trying in app purchases for the first time. My question, why do I get no available product?
I have seen this asked before with either no answers or with answers referencing bad links.
I have a product configured on the iTunes with bundle id.
I have an in app purchase configured for that app.
In my code I do:
productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productidentifiers];
productsRequest.delegate = self;
[productsRequest start] ;
I call that with the bundle ID for that in app purchase (i've tried the app ID too).
This delegate function gets called
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
but products is an empty array.
This does not get called:
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
This does:
- (void)requestDidFinish:(SKRequest *)request
What do I need to check to get the producs?
You have to use a real device, and you must also set up a test account in iTunes Connect.
you should use the IAP product ID instead of app id(or bundle ID). the IAP product ID is created by itune connect. "products is an empty array." means this id(whatever you're use) you requested from itunes store does not exisit.
here's my working code:
#define ProductID_IAPUNLOCKALL #"unlockall.myapp"
NSArray *product=[[NSArray alloc] initWithObjects:ProductID_IAPUNLOCKALL,nil];
NSSet *nsset = [NSSet setWithArray:product];
SKProductsRequest *request=[[SKProductsRequest alloc] initWithProductIdentifiers: nsset];
request.delegate=self;
[request start];
the "unlockall.myapp" is a uniq string defined in itunes connect.