robotmedia / rmstore Goto Github PK
View Code? Open in Web Editor NEWA lightweight iOS library for In-App Purchases
License: Apache License 2.0
A lightweight iOS library for In-App Purchases
License: Apache License 2.0
The completion block of restoreOnSuccess does not provide and productID's of IAP's, so I have to manually add/remove the observe and implement an extra method that gets notifications when products have been purchased/restored. It would simply save some time/code if the completion block returns restored product ID's.
I'm running into following issue when testing on the device with airplane mode on (so without the internet).
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
RMProductsRequestWrapper *wrapper = [self popWrapperForRequest:request];
RMStoreLog(@"products request failed with error %@", error.debugDescription);
if (wrapper.failureBlock)
{
wrapper.failureBlock(error);
}
// here it creates dict with nil error what leads to crash
NSDictionary *userInfo = @{RMStoreNotificationStoreError: error};
[[NSNotificationCenter defaultCenter] postNotificationName:RMSKProductsRequestFailed object:self userInfo:userInfo];
}
The error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]'
Great work with the library btw :)
I'm trying to implement a transition from paid to freemium model for my app, but for some reason originalAppVersion is always empty for me. I was going to use this field to decide whether a person purchased my paid app, so that I can unlock all IAPs for them.
Apple's docs say that: "In the sandbox environment, the value of this field is always “1.0”.". But even if I renew the receipt and then read it using RMAppReceipt, this field is always nil.
If I try to trace it, I see that length is zero after evaluating
ASN1_get_object(pp, &length, &tag, &class, omax);
Is that the behaviour everyone is getting and do you think I can rely that originalAppVersion will be filled correctly in production?
When I build my app for 64-bit CPUs, I get the following warnings from Xcode:
I tried fixing these errors, but it looks like the changing line 64 in RMStoreTransactionReceiptVerificator.m from:
int intLength
to:
NSUInteger intLength
Breaks the unit tests on iOS 6 (which is obviously unacceptable).
I had test demo project on device 6.1 and 6.1.3 and it crashes every times call request products.
After successful restore process, I am getting:
"Cannot connect to iTunes Store"
alert in Simulator.
Hi,
Just looking at your code I found this pattern on several places
NSError *error;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
if (error != nil)
{
// do stuff
I read somewhere that errors returned by reference are not guaranteed to be initialized unless the condition returned by the method is false. If this is the case you should check for data being nil instead of the error being not nil. If the error was indeed not set by the method then it may contain garbage on success. I suppose you actually tested this and it worked for this particular method call but you can not assume that it will always work as per the general rule.
Thanks,
In my app, which is currently using MKStoreKit but which I am transitioning to RMStore, I need to record transactions in the keychain without going through the payment process for certain product identifiers. My app changed from paid to free, so although the paid clients did not purchase the in-app purchase, I have an indicator to tell that they did, in fact, pay for the app. So, for these clients, I 'remember' or add the product to the keychain as if it has been purchased through the in-app purchase process.
As discovered by @John-Lluch in #30, RMStore does not provide a way for verificators to indicate that they couldn't complete the verification (e.g., due to connectivity problems).
Also, RMStoreTransactionReceiptVerificator is calling the failure block when the connection with Apple fails. This finishes the transaction but reports the payment or restoration as a failure, when it might not be.
When I successfully restore a transactions with the following code:
[[RMStore defaultStore] restoreTransactionsOnSuccess:^{
// success handler
} failure:^(NSError *error) {
// failure handler
}
]
And later call the following code to verify that the purchase restoration has been completed:
RMStore *store =[RMStore defaultStore];
RMStoreKeychainPersistence *transactionStore = store.transactionPersistor;
return [transactionStore isPurchasedProductOfIdentifier:@"myProductId"];
I get a result of "false" when I expect it to be "true" after restoring my purchase.
When I look at the RMStore.m code, it looks like there is no call to persist the transaction during the restore process.
Note: I am using RMStoreKeychainPersistence method to persist my IAP transactions.
Can RMStore be used for non-renewing subscriptions?
And does it help out with persisting purchases in iCloud like the Storekit guide suggests?
Thanks in advance
Svend
I just updated my codebase to adopt version 0.4.2 of RMStore and I saw that the way to verify purchases has changed with the introduction of RMStoreKeychainPersistence alongside the old user defaults implementation.
It would be helpful if the wiki or README included a note to explain how to check that a product has already been purchased with the "isPurchasedProductOfIdentifier" API. This doc should include how one needs to configure the [RMStore defaultStore] object needs to have its receiptVerificator and/or transactionPersistor attributes be configured in order for the purchase verification to work.
I was using RMStore from a downloaded file, but I'v changed to pods now and it can't find RMStoreAppReceiptVerificator.h/.m. The problem is at the .podspec it states that the subspec 'AppReceiptVerificator' be available only for iOS 7.0, but my app deployment target is 5.0. What should I do? Change my pod file to 7.0 only for RMStore? Or what? I have configured RMStore just like the demo Proyect with this line:
_receiptVerificator = iOS7OrHigher ? [[RMStoreAppReceiptVerificator alloc] init] : [[RMStoreTransactionReceiptVerificator alloc] init];
[RMStore defaultStore].receiptVerificator = _receiptVerificator;
Thanks
Now when I try to make an In-App Purchase and the product hasn't been requested yet it returns with an error.
I believe it would be really nice if it doesn't have the product yet it automatically requests it first and then proceeds to the purchase automatically.
Otherwise I have to always check if the product is available or not in RMStore before trying the addPayment...
Both methods are useful for apps which want to switch to a freemium model.
although this is stated in RMAppReceipt.h
/** The version of the app that was originally purchased. This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in OS X) in the Info.plist file when the purchase was originally made. In the sandbox environment, the value of this field is always “1.0”.
*/
@Property (nonatomic, strong, readonly) NSString *originalAppVersion;
when i use it on test environment (sandbox) i get no value returned, no "1.0" string as state, is this normal? can i trust it to work properly on production code ?
I have a problem now with my current live app (using MKStoreKit) where, if a user tries to upgrade the app, and they are on a new device (iPhone 5S for example), they are taken to an account verification screen in the actual App Store. Then, once they verify their account, they are returned to my app. Apparently, wherever they are returned to is a place where I do not know, because my app does not deliver the content, and then crashes, and then somehow crashes every time thereafter when they return to the app to try to upgrade or restore their purchase (with a SIGTRAP error?!?), which is unrecognizable to the app at this point. I have not gotten to the bottom of this problem yet, but I was wanting to alert you to the error so that you are aware of it, and can make sure that RMStore handles this situation if it is one that you think is important. Also, if you have any thoughts at all on how this should be handled, I would love to hear them, as I am about at my wits end with this.
In my applications I am using RMStore library. After calling addPayment:success:failure method of RMStore class, I am doing receipt validation locally. But after validation where I will get the transaction receipt. Sample receipt after verification is below.
{
environment = Sandbox;
receipt = {
"adam_id" = 0;
"application_version" = "1.6";
"bundle_id" = "Some bundle id";
"download_id" = 0;
"in_app" = (
{
"is_trial_period" = false;
"original_purchase_date" = "2013-10-10 06:43:45 Etc/GMT";
"original_purchase_date_ms" = 1381387425000;
"original_purchase_date_pst" = "2013-10-09 23:43:45 America/Los_Angeles";
"original_transaction_id" = 1000000089656289;
"product_id" = "product_id";
"purchase_date" = "2013-12-03 08:24:49 Etc/GMT";
"purchase_date_ms" = 1386059089000;
"purchase_date_pst" = "2013-12-03 00:24:49 America/Los_Angeles";
quantity = 1;
"transaction_id" = 1000000089656289;
},
{
"is_trial_period" = false;
"original_purchase_date" = "2013-12-03 08:24:49 Etc/GMT";
"original_purchase_date_ms" = 1386059089000;
"original_purchase_date_pst" = "2013-12-03 00:24:49 America/Los_Angeles";
"original_transaction_id" = 1000000095357076;
"product_id" = "product_id";
"purchase_date" = "2013-12-03 08:24:49 Etc/GMT";
"purchase_date_ms" = 1386059089000;
"purchase_date_pst" = "2013-12-03 00:24:49 America/Los_Angeles";
quantity = 1;
"transaction_id" = 1000000095357076;
}
);
"receipt_type" = ProductionSandbox;
"request_date" = "2013-12-03 08:24:56 Etc/GMT";
"request_date_ms" = 1386059096603;
"request_date_pst" = "2013-12-03 00:24:56 America/Los_Angeles";
};
status = 0;
}
The podspec has a known bug which is that the AppReceiptVerificator subspec is missing the OpenSSL static library.
I don't have a lot of experience with CocoaPods so I asked how to do this in Stack Overflow. Does anyone know the answer or can submit a pull request with a fix?
Thanks!
Hi;
I have 10 products purchased. When I want to restore it, code is waiting tooo much because for every product, it goes to server again to make receipt verification because of the code:
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
SKPaymentTransaction *originalTransaction = transaction.originalTransaction;
SKPayment *payment = originalTransaction.payment;
RMStoreLog(@"transaction restored with product %@", payment.productIdentifier);
_pendingRestoredTransactionsCount++;
/*if (self.receiptVerificator != nil)
{
[self.receiptVerificator verifyReceiptOfTransaction:transaction success:^{
[self verifiedTransaction:transaction];
[self notifyRestoreTransactionFinishedIfApplicableAfterTransaction:transaction];
} failure:^(NSError *error) {
[self failedTransaction:transaction error:error];
[self notifyRestoreTransactionFinishedIfApplicableAfterTransaction:transaction];
}];
}
else
{*/
RMStoreLog(@"WARNING: no receipt verification");
[self verifiedTransaction:transaction];
[self notifyRestoreTransactionFinishedIfApplicableAfterTransaction:transaction];
//}
}
I commented out the verification part. But I still want to verify when user wants to buy a product. So I cannot make receiptVerificator nil.
RMStore already supports iOS 7. However, there are non-breaking API changes in StoreKit that it would be nice to contemplate.
In my entire testing phase for my new app, I never saw this error message before (using one of the included verificators). My app got approved a few hours ago, and I'm trying to buy my own IAP, but I receive the error message in the title when I do it.
But it looks like my IAPs have been unlocked anyway. I check if the purchase was valid by using the receipt itself so it does look like the purchase was successful and that the receipt should be valid.
Am I doing something wrong, or does the app need to refresh something related to IAP, since the app was approved less than 5 hours ago?
Thanks Hermes for creating such a great project.
I would like to ask RMStore can defence iAPFree? iAPFree can intercept iAP receipt request and fake the answer, so it is more powerful than iAP Cracker.
Hi,
First of all thank you for this great component, I was wondering if it was normal that when I add the payment I get payment failed when (The in app purchase has been approved by Apple) ? I don't even get to sign in? Or something wrong in my code
In the simulator I get to sign in but not in the device
Thank You
RMAppReceipt
uses OpenSSL to extract the app receipt as ASN1 data from the PKCS7 container and then parse the ASN1 data into an object.
In order to avoid external dependencies it would be better to use system frameworks instead. Sadly, my cryptography-fu is not strong enough to do it. Any takers?
According to the this app, it appears to be possible.
Provide support for storing purchase data in the Keychain, or allow for a delegate to do it.
RMStore kinda supports subscriptions already. Auto-renewable subscriptions require minor changes to the receipt validation code. Also, a few convenience subscription management methods might be helpful.
Error Domain=SKErrorDomain Code=0 "Cannot connect to iTunes Store" UserInfo=0x14eabc00 {NSLocalizedDescription=Cannot connect to iTunes Store}
Any ideas?
When the user taps to purchase a product in my app, I run:
NSString *productId = @"myProductId";
[[RMStore defaultStore] addPayment:productId success:^(SKPaymentTransaction *transaction) {
NSLog(@"Product purchased: %@", productId);
// ... do stuff here
} failure:^(SKPaymentTransaction *transaction, NSError *error) {
NSLog(@"Purchase failed: %@", error);
// ... throw an error here
}];
This works great under most circumstances. However when the user is prompted for the password, and then taps the 'cancel' button, the failure block is called. Is it possible to determine that it was not a system failure, but rather that the user simply decided to cancel?
When running pod install
for the first time on a project with RMStore the project will not build. You have to link the Pods-RMStore target in the Pods project with the libssl and libcrypto libraries.
Relevant portion of build log:
(null): "_d2i_PKCS7_fp", referenced from:
(null): +[RMAppReceipt dataFromPCKS7Path:] in libPods.a(RMAppReceipt.o)
(null): "_SHA1", referenced from:
(null): -[RMAppReceipt verifyReceiptHash] in libPods.a(RMAppReceipt.o)
(null): "_PKCS7_free", referenced from:
(null): +[RMAppReceipt dataFromPCKS7Path:] in libPods.a(RMAppReceipt.o)
(null): "_ASN1_get_object", referenced from:
(null): _RMASN1ReadInteger in libPods.a(RMAppReceipt.o)
(null): _RMASN1ReadOctectString in libPods.a(RMAppReceipt.o)
(null): _RMASN1ReadString in libPods.a(RMAppReceipt.o)
(null): +[RMAppReceipt enumerateASN1Attributes:length:usingBlock:] in libPods.a(RMAppReceipt.o)
(null): Symbol(s) not found for architecture armv7s
(null): Linker command failed with exit code 1 (use -v to see invocation)
Podfile:
platform :ios, '7.0'
pod 'RMStore', '~> 0.4.2'
Validate the receipt hash in RMStoreAppReceiptVerificator
, as outlined in Validating Receipts Locally.
In RMAppReceipt.m file, the +(NSDate)formatRFC3339String:(NSString)string method
the dateFormat is incompatible with certain settings, example:
If the phone region format setting (General > International > Region Format) is changed to United Kingdom, and the 24-Hour Time Switch is turned off (General > Date & Time).
Because of this, I'm able to get my purchased products' bundle ID, but I cannot get the proper return of NSDate values from [RMAppReceiptIAP originalPurchaseDate], [RMAppReceiptIAP subscriptionExpirationDate], and basically anything that returns a date. It will return me a null value (basically dateformatter failed).
The string before parsing the date looks like this: "2014-02-20T04:27:04Z" without quotes.
And the dateFormat is this: "yyyy-MM-dd'T'HH:mm:ssZ" without quotes.
Changing HH to hh fixes the specfic setting described above (Region Format and 24 hour switch), but I'm not sure if it breaks the other combinations of settings (Different region formats with different 24 hour settings).
Hope you guys can fix this asap :)
Hi Hermes, thanks for sharing this fantastic class. I really like that you added support for local transaction verification on iOS7. I guess this would be a pain to figure out if it wasn't for your class.
I have a couple of questions. I am looking at the class for purchasing consumable products. When an user buys a product I store the product along with the receipt and other data on a server in the cloud. Purchases are in fact liked to activation codes for projects that users develop in the app, so persistence of purchased activations for users is given through the server. I also use the server for receipt validation, so when a purchase is made the server validates it and creates an activation. The first question I have is what happens if the server fails to validate the receipt or to create a code. Since this is a consumable product I assume there is no way to restore purchases, and the user may end having paid for no content, is this right?. In such case, is there a way to prevent users from paying until the app received confirmation from the server? Thanks.
Hi;
I have a question. I couldn't find my answer in the tutorial.
I want to restore purchased products. Before last update, I had the product identifiers of the restored products so that I could download the content.
But in the latest version, using blocks, I don't have the product identifiers. So how will I know which product to restore?
Thank you.
In the WWDC video "Using Store Kit for In-App Purchases", the speaker describes adding a payment.applicationUsername as a hash of the customer account name.
payment.applicationUsername = hash(customerAccountName);
I cannot find any documentation on this but thought that you might like to add it if you can find more information on it.
Validate the receipt signature in RMStoreAppReceiptVerificator
or RMAppReceipt
, as outlined in Validating Receipts Locally.
This kind of notification is equal both for first purchasing and restoring of transaction. Is it possible to add some flag into userInfo as follows:
NSDictionary *userInfo = @{RMStoreNotificationTransaction: transaction, RMStoreNotificationProductIdentifier: productIdentifier, RMStoreNotificationRestoreState:@(YES)};
'transactionReceipt' is deprecated in iOS 7. Will you be including code for the new receipt validation process?
I am testing on a device in iOS 7 in the sandbox. I logged out of my iTunes account and cleared the keychain data. I downloaded a fresh version of the app and used a brand new test account. I hit the Restore button for my non-consumable product. I was asked to enter my iTunes ID and password twice, even though I am positive I entered it correctly the first time. The product was then restored successfully, even though it had not yet been purchased by this test account user. I tried this with multiple test accounts and it is reproducible every time.
Store and check for purchase in iCloud (if available) as well as in the Keychain. iCloud is basically a backup, which can help keep track of app purchases across devices. Use MKStoreKit as a reference.
I seem to be unable to get transaction persistence to work.
[REDACTED]
After I call restoreSuccess the persistence returns null
[[RMStore defaultStore]restoreTransactionsOnSuccess:^{
RMStoreUserDefaultsPersistence *persistence = (RMStoreUserDefaultsPersistence *)([RMStore defaultStore].transactionPersistor); // nil here!!!
BOOL purchased = [persistence isPurchasedProductOfIdentifier:@"pro_version"];
log4Debug(@"restore success?: %@", purchased ? @"YES" : @"NO");
} failure:^(NSError *error) {
}];
What's the possible cause please? Thank you.
RMStore kinda supports OS X already. What's missing is a OS X demo project and support for Mac App Store receipts, which are different.
I'm having a deprecated warning for transactionReceipt under iOS 7.0. Is that normal?
First of all, thanks for the great framework!
Unfortunately I have a problem with checking if purchases were made or not.
[[RMStore defaultStore] isPurchasedForIdentifier:@"..."] fails for me as nothing is ever stored in NSUserDefaults.
Manually calling
[[RMStore defaultStore] addPurchaseForProductIdentifier:@"..."]
before does not help either.
Am I the only one?
3 warnings during Analyze in
void RMKeychainSetValue(NSData *value, NSString *key)
all three are 'value stored to status is never read'
You are referencing SKReceiptRefreshRequest
which is iOS 7+ so this pod fails to build in any earlier versions even though the pod spec says it's good for iOS 5 and above.
/Users/rob/App/Pods/RMStore/RMStore/RMStore.m:116:5: Unknown type name 'SKReceiptRefreshRequest'
I'm not sure if this is a bug or just something I'm doing wrong. I can successfully get the product details for my items when I submit the product ID's (i.e. I can get price etc.).
When I use the same product ID's to attempt a purchase I get an invalid ID error. This doesn't seem to make sense as they are valid and able to request product details.
Add downloadable content (SKDownload) support to RMStore.
I'm using the following code to get the version number:
RMAppReceipt* appReceipt;
appReceipt = [RMAppReceipt bundleReceipt];
if (appReceipt == nil) {
NSLog(@"ERROR! NO APP RECEIPT FOUND");
return nil;
}
else {
return appReceipt.originalAppVersion;
}
Then I want to verify the version. All versions below 1.1 (it's version 1.0 and 1.0.1) should be return TRUE:
if([originalVersionStr isEqualToString:@"1"] || [originalVersionStr isEqualToString:@"1.0"] || [originalVersionStr isEqualToString:@"1.0.1"])
The problem is, that the users from version 1.0 are fine - but 1.0.1 users get a false here. Now I created a new apple id and downloaded the actual version. The appReceipt.originalAppVersion return version 6. I think this is the build number?
Can you offer me a fast fix for my issue?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.