what receipt for auto-renewable subscription should i validate? - ios

i'm developing the server side of an app with IAP, i know that i need to send the receipt from the app to my server when the user do a subscription, then validate the receipt with the app store for the status, date expiration, etc, & give the user the content.
But my question is, what receipt do i need to check for the renew status? i mean, the first time i check the receipt the app store give me back a receipt, status and a latest receipt, this latest receipt is the one that should i use to check the status the next time or should i use always the original receipt? i been testing with both of them and they give me the same status from the app store but i'm not sure of what is the correct way to doing.
Thanks

Today, I have trouble with this problem.
Follow Apple doc here, I used this way to check subscription is expired or not.
+ (BOOL)checkInAppPurchaseStatus
{
// Load the receipt from the app bundle.
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (receipt) {
BOOL sandbox = [[receiptURL lastPathComponent] isEqualToString:#"sandboxReceipt"];
// Create the JSON object that describes the request
NSError *error;
NSDictionary *requestContents = #{
#"receipt-data": [receipt base64EncodedStringWithOptions:0],#"password":#"SHARE_SECRET_CODE"
};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
options:0
error:&error];
if (requestData) {
// Create a POST request with the receipt data.
NSURL *storeURL = [NSURL URLWithString:#"https://buy.itunes.apple.com/verifyReceipt"];
if (sandbox) {
storeURL = [NSURL URLWithString:#"https://sandbox.itunes.apple.com/verifyReceipt"];
}
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:#"POST"];
[storeRequest setHTTPBody:requestData];
BOOL rs = NO;
//Can use sendAsynchronousRequest to request to Apple API, here I use sendSynchronousRequest
NSError *error;
NSURLResponse *response;
NSData *resData = [NSURLConnection sendSynchronousRequest:storeRequest returningResponse:&response error:&error];
if (error) {
rs = NO;
}
else
{
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:resData options:0 error:&error];
if (!jsonResponse) {
rs = NO;
}
else
{
NSLog(#"jsonResponse:%#", jsonResponse);
NSDictionary *dictLatestReceiptsInfo = jsonResponse[#"latest_receipt_info"];
long long int expirationDateMs = [[dictLatestReceiptsInfo valueForKeyPath:#"#max.expires_date_ms"] longLongValue];
long long requestDateMs = [jsonResponse[#"receipt"][#"request_date_ms"] longLongValue];
NSLog(#"%lld--%lld", expirationDateMs, requestDateMs);
rs = [[jsonResponse objectForKey:#"status"] integerValue] == 0 && (expirationDateMs > requestDateMs);
}
}
return rs;
}
else
{
return NO;
}
}
else
{
return NO;
}
}
Hope this help.

after completion of your transaction you will get tansaction & trasaction.reciept for this transaction reciept you want to provide base64 here is the code for this
NSString *jsonObjectString = [self encodeBase64:(uint8_t*)transaction.transactionReceipt.bytes
length:transaction.transactionReceipt.length];
this jsonObjectString will save it to the server and while verifying the receipt you want to provide sharedsecret for this verify this link

Related

In App Purchase Objective C Status from appStoreReceiptURL

I am new in Objective c I found
NSURL* url = [[NSBundle mainBundle] appStoreReceiptURL];
NSLog(#"receiptUrl %#",[url path]);
for check In App Purchase Status for validation or something like this.
how can I get this : as NSString ( INITIAL_BUY,CANCEL)
.INITIAL_BUY Initial purchase of the subscription.
.CANCEL Subscription was canceled by Apple customer support.
.RENEWAL Automatic renewal was successful for an expired subscription.
.INTERACTIVE_RENEWAL Customer renewed a subscription interactively after it lapsed, either by using your app’s interface or on the App Store in account settings.
.DID_CHANGE_RENEWAL_PREFERENCE Customer changed the plan that takes affect at the next subscription renewal.
The url you have, is just the location of the receipt, so you first need to read the receipt file:
- (NSData *)loadReceipt
{
LogMethodCall
NSURL *url = NSBundle.mainBundle.appStoreReceiptURL;
if (!url) return nil;
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:url options:0 error:&error];
if (data) return data;
NSLog(#"Error loading receipt data: %#", error.localizedDescription);
return nil;
}
Now you have the receipt data, you can convert it to NSDictionary and view its contents:
NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:[self loadReceipt]options:0 error:&error];
if (!error) {
NSLog(#"%#", json);
} else {
NSLog(#"%#", error);
}

How can I read offline receipt inpp purchase in ios?

I have implemented Inapp purchase in my app and validating receipt but I have problem with validate receipt when i am on offline.How can i do that?
Following is my code to validate when my interent is connected.
-(void)refreshRecipt
{
NSError *error;
_isUserActive = NO;
NSURL *recieptUrl = [[NSBundle mainBundle]appStoreReceiptURL];
NSError *recieptError;
BOOL isPresent = [recieptUrl checkResourceIsReachableAndReturnError:&recieptError];
if (!isPresent)
{
SKReceiptRefreshRequest *ref = [[SKReceiptRefreshRequest alloc]init];
ref.delegate =self;
[ref start];
return;
}
NSData *reciptData = [NSData dataWithContentsOfURL:recieptUrl];
if (!reciptData)
{
return;
}
dicPayload = [NSMutableDictionary dictionaryWithObject:[reciptData base64EncodedStringWithOptions:0] forKey:#"receipt-data"];
[dicPayload setObject:#"21f843e264474b68b3a81c6b7ca19938" forKey:#"password"];
NSData *requestData = [NSJSONSerialization dataWithJSONObject:dicPayload
options:0
error:&error];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:verifyRecieptURL]];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:requestData];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
NSLog(#"error");
} else {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if ([jsonResponse objectForKey:#"latest_receipt_info"])
{
NSArray *array = [jsonResponse objectForKey:#"latest_receipt_info"];
NSLog(#"%#",array);
NSDictionary *latestDetail = [array lastObject];
if ([latestDetail objectForKey:#"is_trial_period"])
{
if ([[latestDetail objectForKey:#"is_trial_period"] isEqualToString:#"true"])
{
_isFreeTrialActive = YES;
}
else
{
_isFreeTrialActive = NO;
}
_isUserActive = [self calculateCurrentSubscriptionActive:[latestDetail objectForKey:#"expires_date_ms"]];
if (_isUserActive)
{
NSLog(#"User is active");
_SubscriptionActive = YES;
}
else
{
_SubscriptionActive = NO;
NSLog(#"User is not active");
}
}
else
{
NSLog(#"no purachase done,first time user!");
}
}
}
}];
}
please help me to sort out this.
You can validate you receipt locally, this is how you would do it according to apple documentation:
Validate the Receipt
To validate the receipt, perform the following tests, in order:
1- Locate the receipt.
If no receipt is present, validation fails.
2- Verify that the receipt is properly signed by Apple.
If it is not signed by Apple, validation fails.
3- Verify that the bundle identifier in the receipt matches a hard-coded constant containing the CFBundleIdentifier value you expect in the Info.plist file.
If they do not match, validation fails.
4- Verify that the version identifier string in the receipt matches a hard-coded constant containing the CFBundleShortVersionString value (for macOS) or the CFBundleVersion value (for iOS) that you expect in the Info.plist file.
If they do not match, validation fails.
5- Compute the hash of the GUID as described in Compute the Hash of the GUID.
If the result does not match the hash in the receipt, validation fails.
If all of the tests pass, validation passes.
In order to do so you would first need to decrypt the receipt. For this you can use a library like RMStore. Using this library you can do something like:
- (RMAppReceipt *)validReceipt {
RMAppReceipt *receipt = [RMAppReceipt bundleReceipt];
if (!receipt)
//No receipt
return nil;
if (![receipt.bundleIdentifier isEqualToString:#"Your bundle ID"]) {
//Receipt is invalid
return nil;
}
if (![receipt verifyReceiptHash]) {
//Receipt is invalid
return nil;
}
//Receipt is valid
return receipt;
}
RMAppReceipt contains all information from the receipt.

Validating App store receipt gives DrmInvalidArgumentException

I'm following the documentation here.
https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html
I have a receipt-data from my team that I'm trying to validate, they're getting error code 21002 which was malformed JSON. It looks like they had extra parameters appended to the base64 data, so I tried removing those and sending:
- (void)viewDidLoad {
[super viewDidLoad];
NSData *receipt; // Sent to the server by the device
// Create the JSON object that describes the request
NSError *error;
NSDictionary *requestContents = #{
#"receipt-data": #"<<$mybase64data>>", #"password" : #"<<$thepassword>>"};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error];
if (!requestData) { /* ... Handle error ... */ }
// Create a POST request with the receipt data.
NSURL *storeURL = [NSURL URLWithString:#"https://buy.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:#"POST"];
[storeRequest setHTTPBody:requestData];
// Make a connection to the iTunes Store on a background queue.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
/* ... Handle error ... */
NSLog(#"conerror %#", connectionError);
} else {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
NSLog(#"hello %#", jsonResponse);
NSLog(#"error %#", error);
if (!jsonResponse) {
}
}
}];
}
result:
2017-03-03 22:45:47.454 receipttest[89851:352604] hello {
exception = "com.apple.jingle.mzfairplay.validators.DrmInvalidArgumentException";
status = 21002;
}
2017-03-03 22:45:47.455 receipttest[89851:352604] error (null)
Something to keep in mind: In this example the data is sent to Apple straight from the App, but you might do this from a server too. When you testing your server App don't use NSLog() to print your base64 data, NSLog truncates data.
I had this issue when using a receipt from a test user that was a couple days old with a yearly auto-renewable subscription.
I checked the above helpful responses about extra characters etc (I also checked I had supplied my app secret for auto-renewable) with no joy.
In the end I tried creating A NEW SANDBOX user and it worked first time
with no other changes other than the new Receipt!
Hope this helps someone.
I also received the same error response for serveral receipts. The solution for me was to remove all occurrences of \r\n.
Maybe you have the same issue. I still haven't figured out when and why these chars are inserted.
Pay attention to URLENCODE:
- (NSString *)URLEncodedString
{
// CharactersToBeEscaped = #":/?&=;+!##$()~',*";
// CharactersToLeaveUnescaped = #"[].";
NSString *unencodedString = self;
NSString *encodedString = (NSString *)
CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)unencodedString,
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8));
return encodedString;
}

Returns many transactions on iOS In-App-Purchases receipt validation

My app contains consumable IAP products, returns more than one transactions when I call validation receipt with this code:
[[NSBundle mainBundle] appStoreReceiptURL];
Is there any way to return only last transaction?
Is it related about restoring transactions?
I checked this Multiple receipt count for restoreCompletedTransaction inapp purchasing and this iOS in-app-purchase restore returns many transactions.
I tried to restore all purchases but it didn't work.
I'm using these lines for calling receipt:
- (void) checkReceipt {
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if(!receipt) {
}
NSError *error;
NSDictionary *requestContents = #{#"receipt-data": [receipt base64EncodedStringWithOptions:0]};
NSLog(#"requestContents:%#", requestContents);
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
options:0
error:&error];
if (!requestData) { }
NSURL *storeURL = [NSURL URLWithString:#"https://sandbox.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:#"POST"];
[storeRequest setHTTPBody:requestData];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
} else {
}
}];
}
Note: This app supports iOS 8+.
It's not related to restoring transactions, it is because apple responds with array of all in-app transactions made by the user when making a validation request. The same information is contained in the receipt if you decode it locally.
If you are looking for the last transaction made you can sort the array ascending by the purchase_date_ms and take the last one.
My objective-c is not so hot so I can't help you with sorting but this document may help: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Collections/Articles/Arrays.html

iOS: forcing server to provide updated data

I'm working on an iPad app that requests data from a server, changes and submits it, and then re-requests the data from the server, displaying it to the user. The app updates the data just fine (the equivalent web app sees the update happening), but the data that the iPad app gets back is the old data. I thought maybe it was the caching flag on the NSURLRequest, but it doesn't look like it.
Here is my sequence of calls:
NSString* currentStuff = self.fCurrentIndex.currentStuff;
NSError* err = nil;
[self.fCurrentIndex approve:currentStuff withUsername:username andPassword:password error:&err];
if (err == nil)
{
// rebuild the case list (grab the data from the URL again first)
[self getCaseListViaURL]; // grab the updated data
[self setupUIPanel]; // display it
}
Here's the code that grabs the data (the 'getCaseListViaURL' call):
NSURLResponse* response;
NSError* err = nil;
NSMutableDictionary * jsonObject = nil;
NSString * urlRequestString;
urlRequestString = [method to get the URL string];
NSURL * url = [NSURL URLWithString:urlRequestString];
NSURLRequest * request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60];
NSData * data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&err];
if (err == nil)
{
jsonObject = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&err];
}
if (err && error) {
*error = err;
}
return jsonObject;
Is there any way to force the server to serve up the updated data?
Thanks in advance.
EDIT: Per comments, I'm adding the sequence of update to the server and subsequent pull:
This does the push to the server:
NSString* currentStuff = self.fCurrentIndex.currentStuff;
NSError* err = nil;
[self.fCurrentPatient approveStuff:currentStuff withUsername:username andPassword:password error:&err];
Where 'approveStuff' eventually calls:
__block NSData * jsonData;
__autoreleasing NSError * localError = nil;
if (!error) {
error = &localError;
}
// Serialize the dictionary into JSON
jsonData = [NSJSONSerialization dataWithJSONObject:data
options:NSJSONWritingPrettyPrinted
error:error];
if (*error) return nil;
NSURLResponse* response;
NSString * urlRequestString;
urlRequestString = [self urlStringForRelativeURL:relativeURL
withQueryParams:params];
NSURL * url = [NSURL URLWithString:urlRequestString];
NSMutableURLRequest * request;
request = [NSMutableURLRequest requestWithURL:url
cachePolicy:self.cachePolicy
timeoutInterval:self.timeOutInterval];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:jsonData];
[request setValue:#"application/json;charset=UTF-8" forHTTPHeaderField:#"Content-Type"];
jsonData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&localError];
NSMutableDictionary * jsonObject;
if (localError == nil)
{
jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&localError];
}
if (error && localError) {
*error = localError;
}
return jsonObject;
Right after this, I call the aforementioned get call and rebuild the UI. Now, if I stick a breakpoint when I do the get, and check on the web server if the data is updated after the push, I see the data is there. However, when I let the get operation continue, it gives me the old data.
So it looks like the issue was on the server. There were some data structures on the server side that weren't being refreshed when the data was being posted.

Resources