Plugin.InAppBilling InAppBillingPurchase.PurchaseToken is sometimes null in production - xamarin

I'm using James Montemagno's Plugin.InAppBilling library for Xamarin to do iOS and Android in-app subscriptions. It has worked mostly correctly so far, except that once in a while, on iOS only, InAppBillingPurchase.PurchaseToken comes back null from calls to PurchaseAsync and GetPurchasesAsync.
For example, in my Restore Purchases logic, I have code similar to this:
var purchases = await CrossInAppBilling.Current.GetPurchasesAsync(ItemType.Subscription);
// Sometimes we receive purchases with no PurchaseToken.
// Can't verify the purchase without a token.
var verifiable = purchases.Where(p => !string.IsNullOrWhiteSpace(p.PurchaseToken));
At this point, verifiable sometimes has a different count (0) than purchases (1).
So far, this has seemed to happen when the user purchased using either store credit or a gift card, and only on iOS.
Also, I'm not sure if this bears on the problem, but I am not using the overloads of PurchaseAsync and GetPurchasesAsync which take IInAppBillingVerifyPurchase, because I use server-side verification exclusively (no client-side verification). My workflow is to make the purchase, add the resulting InAppPurchase object to a queue for processing, and then send it to our server as a separate step, for validation and association with the user's account. However, if this is not a valid workflow or if it is known that sometimes PurchaseToken data will be available via IInAppBillingVerifyPurchase.VerifyPurchase but not attached to the InAppBillingPurchase objects returned from the methods above, I would certainly like to know about that. (For what it's worth, I've read the documentation and don't see anything that suggests this.)
Thanks in advance for any help.

Okay, I think I've learned enough to provide useful information for anyone else dealing with this issue.
First, I've figured out what Apple means by "iOS 6 style" and "iOS 7 style" receipts. These do not refer to the version of iOS that creates the receipts. (My up-to-date iOS 12 device still can and does generate "iOS 6 style" receipts.) Instead, these refer to two different receipt formats that were introduced in the respective iOS versions.
iOS 6 style receipts come from SKPaymentTransaction.transactionReceipt and contain information about one specific transaction. This field is now deprecated by Apple.
iOS 7 style receipts come from the app bundle, via the location named in NSBundle.mainBundle.appStoreReceiptUrl. These receipts contain a full manifest of all purchases ever made by the user. The receipts don't expire either -- you can always send them to Apple for validation (though, obviously, individual transactions contained within might show as expired in the response). These are the receipts you should prefer.
The reason this is important is that if you're using the Plugin.InAppBilling library, the InAppBillingPurchase object you get from calling something like PurchaseAsync contains the deprecated iOS 6 style receipt in its PurchaseToken field.
I'm still not sure why it is sometimes present and sometimes null, but seeing as the underlying source of the data is deprecated, it's probably safe to assume that this can and will happen. Therefore, it probably makes sense to cut over to the iOS 7 style receipts as soon as possible.
Note that when you call PurchaseAsync, if you specify an implementation of IInAppBillingVerifyPurchase, your IInAppBillingVerifyPurchase.VerifyPurchase method will receive the newer iOS7 receipt instead. However, the InAppBillingPurchase object returned by PurchaseAsync still gets an iOS 6 style receipt (if it gets anything at all).
Personally, I like the InAppBillingPurchase object itself. It has useful information packed into a convenient package. Since I want to keep the serialized InAppBillingPurchase objects in a queue so that I can retry validation if there are problems with our servers, connectivity, etc, what I'm doing is immediately replacing the PurchaseToken property with the iOS 7 style receipt that I manually retrieve from the bundle.
If you do this, be sure that your code handles the slightly different formats of the iOS 6 and iOS 7 receipts properly. (Our earlier attempts had some errors stemming from not properly understanding what these terms meant.)
I hope this is useful to someone out there. Good luck!

Related

Multiple - isolated - WkWebViews in the same app - macOS

Let's say I want to show two WkWebView side by side, each logged in to the same website, but with two different accounts. The logins (and other data) should be persisted between launches
According to this 2017 wwdc talk, it can be done. This is the result I get: they share data.
And this is what I want, not sharing data.
According to the speaker, it's possible. He just doesn't say how.
A few approaches I've tried
Instantiating a new WkWebView. That made no difference. They still share data. I tried instantiating both from a storyboard and in code.
Changing my bundle identifier. Not a viable solution as I want the two datastores to run side by side. But it works, and also it seems the data (sans cookies) is stored in /Users/username/Library/WebKit/(bundle-identifier)
Instantiating a new WKProcessPool with webView.configuration.processPool = WKProcessPool(). Made no difference.
Handling cookies myself. This would be my last resort. I'd set the WKWebsiteDataStore to be the .nonPersistent() one, and save and delete cookies in my own store on open and close (the cookie observer doesn't trigger in nonPersistent). Bad in three ways: 1. I need a secure place to store them - (Keychain)? 2. It won't persist whatever the sites may have in Local Storage, 3: added work and complexity

How to share information between cooperating Firefox WebExtensions

I want to create a straightforward extension for Firefox.
User hovers over some word on any page
Pull the dictionary definition of that word from a file inside the extension
Display it while still hovered
I am new to Firefox add-ons and WebExtensions, so here's what I'm wondering:
I want the dictionary file(s) used by the extension to be local, instead of referring to some online website each time.
Any given user might be interested in a different part of the entire dictionary (it contains entries in different languages, users might only want their own 1 or 2 languages) so I want to avoid forcing every user to download the entire dictionary base.
The way I have seen similar add-ons handle that before Firefox 5.* is that they offer the search-and-display add-on separately from the dictionary files which are each available as add-ons in their own right, only actually doing stuff if you have the master add-on installed.
However, none of those examples seem to have been updated for the WebExtensions API and do not support more recent versions of Firefox.
I have also been unable to find how to communicate between web extensions so far.
My question being, how can I share information between 2 or more coorperating extensions to achieve what I described.
And actually, if this seems really stupid for some reason I'm unaware of, do point out any more reasonable alternatives that allow me to handle the dictionary files separately from the main extension.
Possibly related questions I found:
Communicating between 2 Firefox Add-Ons (Cross-Extension Communication)
This one is from 2010 however, thus out of date as far as I could tell.
Mozilla Addon Development - Communicating between windows with different domains
Kind of a similar situation, but they want to pull the definitions from an online source, rather than a local one.
The closest thing I found on the Mozilla browser extension website is communicating between add-on and some native applications, not quite what I need I think.
Communicating between add-ons is a normal part of the functionality of runtime.sendMessage(), runtime.connect(), runtime.onMessage, and runtime.onConnect.
Both runtime.sendMessage() and runtime.connect() have as their optional first parameter:
extensionId
For runtime.sendMessage(), this is:
string. The ID of the extension to send the message to. Include this to send the message to a different extension. If the intended recipient has set an ID explicitly using the applications key in manifest.json, then extensionId should have that value. Otherwise it should have the ID that was generated for the intended recipient.
If extensionId is omitted, the message will be sent to your own extension.
For runtime.connect(), this is:
string. The ID of the extension to connect to. If the target has set an ID explicitly using the applications key in manifest.json, then extensionId should have that value. Otherwise it should be have the ID that was generated for the target.
Both runtime.onMessage, and runtime.onConnect provide a sender property or parameter, either with the message, or as part of the port. This parameter/property is a runtime.MessageSender which includes an id property which is:
id
string. The ID of the extension that sent the message, if the message was sent by an extension. If the sender set an ID explicitly using the applications key in manifest.json, then id will have this value. Otherwise it will have the ID that was generated for the sender.
Note that in Firefox, before version 54, this value was the extension's internal ID (that is, the UUID that appears in the extension's URL).

Branch.io: detect first install from Unity

Trying to detect the first install event from branch.io link (succesfully implement link creating and sharing). I am using Unity branch sdk. The feature i try to create:
user_1 creates and share link to user_2.
When user_2 opens the link and install app i need to reward both of them (with inner in-game coins)
So i succesfully implement the 1. but I cant understand how to detect is user_2 installs the app or simply open it. All data that comes from branch.io UniversalObject callback doesn't contain information that i need.
Which the correct way to detect the install from code?
Alex from Branch here.
The callback parameter you need is +is_first_session. This is one of the parameters returned when the Branch session is initiated each time your app opens (you can find all all these parameters here). If this returns true, then that device has just installed the app (instead of opening it).
However, note that when these parameters are returned, it's impossible to immediately determine if the user is new (what you want), the device is new (not what you want, since the reward could be given twice if the user has installed on multiple devices), or neither (the user deleted the app and reinstalled on the same device). You would probably want to hold off on actually awarding the referral points until after the user has logged in with some sort of unique ID.
Branch also has a built-in feature for tracking referral points that might be useful. That lets you configure all the rules using the dashboard UI instead of needing to do it programmatically inside your app.

Windows store - certification failed because 'didn't seem useful'

I tried publish my app: http://pomidoro.codeplex.com/ on Windows Store and certification failed because:
'Your app doesn't meet requirement 1.1.
Comment from tester: This app didn't appear to provide value or didn't seem useful to the reviewer.'
Is that means that I do not have any chances to publish it anymore?
No, you can make some tweaks and publish again. Here are some thoughts:
It may be an issue of marketing, and modifying the description that you submit with your application will fix the issue. Your description at http://pomidoro.codeplex.com is pretty good, but maybe you could add some data around how it helps you work more efficiently in case the reviewers aren't familiar with Pomodoro. There are tips on writing a good description/app listing here.
Otherwise, you could add some extra features to the app so it's more than just a timer. Can the user record their whole To Do list in the app, and then choose what to focus on during the pomodoro from that? Or, allow them to rank each pomodoro as effective/not effective after the timer goes off and keep historical data so they can see what times of day they are most effective? And so on...
Here is the first post in a blog post series on common certification failures for the Windows Store and how to fix them which might also be useful.
Good luck!

BackgroundTransferService: what are some of the details of its operation?

I kick off 5 uploads (of varying size) via the BackgroundTransferService. I have the following questions about the way it works:
It seems that on the emulator it does 2 uploads at a time. Is this how it works on the actual device? Can I programmatically change this behavior?
Can I count on the uploads going out in the order that I submitted them? I seem to be getting conflicting results in my testing.
When I inspect the BackgroundTransferService in my application, does it contain requests from other apps as well or just mine?
Do I need to reconnect events for all the BackgroundTransferRequest objects when coming back from being tombstoned? What about coming back from being reactivated?
Do I need to disconnect events from the BackgroundTransferRequest when I remove it from the BackgroundTransferService.Requests collection?
When I try to upload a non-existing URL:Port (on the localhost), the TransferStatus is reported as WaitingForNonVoiceBlockingNetwork. The upload never actually completes/fails. Is this how it is on the device? Should I remove the request when it encounters this TransferStatus?
You cannot influence the behaviour of the BTS. If you don't like the way it works you can write the transfer functionality as part of your own application but then you have to handle running in the background yourself.
There is no guarantee on sequence.
The BTS may be handling requests from other apps but you won't be able to see the details. Requests() will only return details for your app.
Surely a quick test will tell you this.
It's good practice to.
Have you checked the TransferError property whe you reach this situation? This is a perfectly valild status in other situations and so you shouldn't treat this as a automatic fail.

Resources