Google Play in-app billing version 3: crash on "item already owned" and missing failure notifications - google-play

After (eventually) shipping a v2 implementation of the Google Play in-app billing, I've had nothing but problems with it post-launch. Dropped transactions, crashes, unable to restore, crazy errors like "can't download, you already own this item", and all sorts of other ridiculous things. Honestly, I've integrated IAB on iOS, Amazon App Store, Samsung Apps and Blackberry 10 now and the Google Play code has taken more time than all the others combined. Times ten. It's just terrible.
Anywayyyy, I've decided to try and implement v3 into my app. The integration process was much, much simpler, so kudos to Google for that. Also, restoring previous transactions now works as expected so that's great. However, I've got a couple of show-stopping problems:
When the user dismisses the IAB dialog (i.e. tapping outside of the dialog borders), I don't receive any notification of this. I would expect to receive some kind of "user cancelled" failure event, but nothing is fired to onIabPurchaseFinished, onConsumeFinished or onQueryInventoryFinished. As a result my app doesn't respond to this and I'm left with a dirty great unused Activity on the screen. Am I missing some kind of "dialogIsFinished" event?
When the user tries to purchase an item that they already own, the app crashes. Unbelievably it looks like this is the intended behaviour, as there's something alluding to this printed to the console ("In-app billing error: Unable to buy item, Error response: 7:Item Already Owned"). I understand that I'm supposed to query for restorable transactions at launch, but this isn't a solution as it's conceivable the user can navigate to the purchase flow of my UI before the restore operation finishes. Surely this should be a non-hard stop, like a dialog box or something? Am I doing something wrong here? I simply can't understand that somebody at Google thinks that this situation deserves a hard crash...
Thanks very much (in advance) for your help. I'm more than happy to share code if you think it's necessary, although my questions seem to be more about the functional design more than anything else. I'm hoping that I'm doing something wrong here, as it's inconceivable to me that a company as capable as Google would re-write this entire system and still have such massive holes all over the place... :-/
Thanks again,
Ben

Hmm, that was my mistake. When I wrote launchPurchaseFlow(), I ended up missing some cleanup code on failure cases. Not only there, but also on a couple of catch{} clauses after that. Thanks for pointing that out! This has just been fixed in the source repository: http://code.google.com/p/marketbilling

I had the same error, I accidentally forgot to consume the item after I purchased. But when I tried to purchase another of same item App crashed.
I dig through the Google IabHelper class and found out that this statement is not handled correctly. I made some small change and now it works. Instead of crashing send error message back with listener.
Here is the modified part of the code. It's in launchPurchaseFlow() method. I'm not sure that I did something good by changing the code it looked like needed. Hope it helps.
try {
logDebug("Constructing buy intent for " + sku);
Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, ITEM_TYPE_INAPP, extraData);
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
logError("Unable to buy item, Error response: " + getResponseDesc(response));
result = new IabResult(response, "Unable to buy item");
if (listener != null) listener.onIabPurchaseFinished(result, null);
/* Finish Current Async Task*/
flagEndAsync();
} else {
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
mRequestCode = requestCode;
mPurchaseListener = listener;
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode, new Intent(),
Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
}

Related

MacOS not responding to MPRemoteCommandCenter commands in the background

I am writing an application for my own purposes that aims to get play pause events no matter what is going on in the system. I have gotten this much working
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.togglePlayPauseCommand.isEnabled = true
commandCenter.togglePlayPauseCommand.addTarget { (MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus in
print("Play Pause Command")
return .success
}
commandCenter.nextTrackCommand.isEnabled = true
commandCenter.nextTrackCommand.addTarget { (MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus in
print("NextTrackCommand")
return .success
}
commandCenter.previousTrackCommand.isEnabled = true
commandCenter.previousTrackCommand.addTarget { (MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus in
print("previousTrackCommand")
return .success
}
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { (MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus in
print("playCommand")
return .success
}
MPNowPlayingInfoCenter.default().playbackState = .playing
Most of those methods are there because apparently you will not get any notifications without having nextTrackCommand or previousTrackCommand or playCommand implemented.
Anyways my one issue is that as soon as you open another application that uses audio these event handlers stop getting called and I cant find a way to detect and fix this.
I would normally try doing AVAudioSession things to state this as a background application however that does not seem to work. Any ideas on how I can get playpause events no matter what state the system is in?
I would like to be able to always listen for these events OR get an indication of when someone else has taken control of the audio? Perhaps even be able to re-subscribe to these play pause events.
There's an internal queue in the system which contains all the audio event subscribers. Other applications get on top of it when you start using them.
I would like to be able to always listen for these events
There's no API for that but there's a dirty workaround. If I understand your issue correctly, this snippet:
MPNowPlayingInfoCenter.default().playbackState = .paused
MPNowPlayingInfoCenter.default().playbackState = .playing
must do the trick for you if you run it in a loop somewhere in your application.
Note that this is not 100% reliable because:
If an event is generated before two subsequent playbackState state changes right after you've switched to a different application, it would still be catched by the application in the active window;
If another application is doing the same thing, there would be a constant race condition in the queue, with unpredictable outcome.
References:
Documentation for playbackState is here;
See also a similar question;
See also a bug report for mpv with a similar
issue (a pre-MPRemoteCommandCenter one, but still very valuable)
OR get an indication of when someone else has taken control of the audio
As far as I know there's no public API for this in macOS.

Non helpfull error message Calabash with page objects pattern

I'm currently using Calabash framework to automate functional testing for a native Android and IOS application. During my time studying it, I stumbled upon this example project from Xamarin that uses page objects design pattern which I find to be much better to organize the code in a Selenium fashion.
I have made a few adjustments to the original project, adding a file called page_utils.rb in the support directory of the calabash project structure. This file has this method:
def change_page(next_page)
sleep 2
puts "current page is #{current_page_name} changing to #{next_page}"
#current_page = page(next_page).await(PAGE_TRANSITION_PARAMETERS)
sleep 1
capture_screenshot
#current_page.assert_info_present
end
So in my custom steps implementation, when I want to change the page, I trigger the event that changes the page in the UI and update the reference for Calabash calling this method, in example:
#current_page.click_to_home_page
change_page(HomePage)
PAGE_TRANSITION_PARAMETERS is a hash with parameters such as timeout:
PAGE_TRANSITION_PARAMETERS = {
timeout: 10,
screenshot_on_error: true
}
Just so happens to be that whenever I have a timeout waiting for any element in any screen during a test run, I get a generic error message such as:
Timeout waiting for elements: * id:'btn_ok' (Calabash::Android::WaitHelpers::WaitError)
./features/support/utils/page_utils.rb:14:in `change_page'
./features/step_definitions/login_steps.rb:49:in `/^I enter my valid credentials$/'
features/04_support_and_settings.feature:9:in `And I enter my valid credentials'
btn_ok is the id defined for the trait of the first screen in my application, I don't understand why this keeps popping up even in steps ahead of that screen, masking the real problem.
Can anyone help getting rid of this annoyance? Makes really hard debugging test failures, specially on the test cloud.
welcome to Calabash!
As you might be aware, you'll get a Timeout waiting for elements: exception when you attempt to query/wait for an element which can't be found on the screen. When you call page.await(opts), it is actually calling wait_for_elements_exist([trait], opts), which means in your case that after 10 seconds of waiting, the view with id btn_ok can't be found on the screen.
What is assert_info_present ? Does it call wait_for_element_exists or something similar? More importantly, what method is actually being called in page_utils.rb:14 ?
And does your app actually return to the home screen when you invoke click_to_home_page ?
Unfortunately it's difficult to diagnose the issue without some more info, but I'll throw out a few suggestions:
My first guess without seeing your application or your step definitions is that #current_page.click_to_home_page is taking longer than 10 seconds to actually bring the home page back. If that's the case, simply try increasing the timeout (or remove it altogether, since the default is 30 seconds. See source).
My second guess is that the element with id btn_ok is not actually visible on screen when your app returns to the home screen. If that's the case, you could try changing the trait definition from * id:'btn_ok' to all * id:'btn_ok' (the all operator will include views that aren't actually visible on screen). Again, I have no idea what your app looks like so it's hard to say.
My third guess is it's something related to assert_info_present, but it's hard to say without seeing the step defs.
On an unrelated note, I apologize if our sample code is a bit outdated, but at the time of writing we generally don't encourage the use of #current_page to keep track of a page. Calabash was written in a more or less stateless manner and we generally encourage step definitions to avoid using state wherever possible.
Hope this helps! Best of luck.

I am trying to add a new gateway to CS-cart and it seems having troubles when i am sent back from the gateway

basically my script so far send values to the gateway then get redirected to CS cart .. in that page i grab the values returned and manipulate them.
i use fn finish and fn change order status to finish the order but no matter what i do i get a 404 page not found . i've tried redirecting to the order page but its creates a problem.
Here is the code i use when returning from gateway.
$StaTus_message = "<br>Thank you for shopping with us. Your credit card has been charged and your transaction is successful. We will be shipping your order to you soon.";
$pp_response['customer_email'] = $_REQUEST['billing_cust_email'];
$pp_response['client_id'] = $_REQUEST['billing_cust_name'];
$pp_response['order_status'] = 'C';
$pp_response['reason_text'] = $StaTus_message;
fn_finish_payment($_REQUEST['Order_Id'], $pp_response);
fn_change_order_status($_REQUEST['Order_Id'], $pp_response['order_status']);
I know this is not a popular subject but i thought I'll give it a go.
Also I've being searching everywhere for documentation both at CS-cart's forum and the internet and couldn't find much.
Thanks in advanced.
Okay. So, the solution to that was exiting the script after the script sent the client to the gate way, and then upon re-enter using fn_change_order_status - to whatever you need, and then using fn_order_placement_routines to actually finalize the order and send email to client/merchant.
Hope that help people out there as I spent nearly 4 days to try and understand that.

Suggestions needed for architecting my code

Background
I'm writing an part of my app that has no UI. It sits in the background watching what you do and timing your work.
There should be no overlapping times, and there should be no breaks in the time data. If there are either of these things, the app has a bug somewhere and I need to be notified.
What I Want
A class called JGDataIntegrityController that does the following:
Check the data store for duplicate times. Scan since the last Duplicate Report Date stored in NSUserDefaults.
If duplicate times are found, build a report.
Send the report.
If the sending isn't successful, then exit. Otherwise continue.
Remove the duplicates
Update the last Duplicate Report Date in NSUserDefaults
Repeat the above for data breaks.
What I've Got
I've made a base class that does all the hard work of sending the report.
Class Diagram http://synapticmishap.co.uk/ReportClasses.jpg
JGReportSender has the following code:
-(void)postReport:(NSString *)report {
NSMutableDictionary *form = // Dictionary Holding Report;
NSURLRequest *request = [NSURLRequest requestWithURL:#"http://postURL" postForm:form];
[NSURLConnection connectionWithRequest:request delegate:self];
}
Where I'm Getting Stuck
What should I do when the report has been sent?
The delegate methods:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError*)error
are called when the report has been sent. But how should I communicate with JGDataIntegrityController?
My Crap Idea
My idea is to have a reportStatus NSNumber property in JGReportSender. Then when the delegate methods get called, this is updated.
reportStatus = 1 means "report sent OK".
reportStatus = 2 means "problem sending report".
Then I could add an observer for reportStatus for JGDataDuplicateReportSender and JGDataBreakReportSender. This would then handle the report sending error or continue on.
Any Good Ideas?
I get the feeling this is a really messy way of doing this. I also feel like I'm overlooking something really obvious.
Any ideas how to do this in a neat way?
Update
I totally forgot to mention - this will be a 100% opt in feature. It'll be disabled by default. It'll also have 3 levels of privacy - from "a data break occurred" through to "a data break occurred after this application was active with this document path". And the reports will also be anonymous.
I'm conscious of all the privacy concerns - this is so I can make the software better, not so I can spy on people!
Give the report sender a delegate property and protocol, with at least two methods: reportSenderDidSucceed: and reportSender:failedWithError:. The report sender will send the latter message from its connection:didFailWithError: method, passing along the error object it got.
I do hope you'll make this feature optional. Expect lots of angry/curious email from users (not to mention public warnings of “don't use this app because it phones home” on web pages) if you don't.
Just a quick note to say if anyone wants a good tutorial on implementing your own delegates as Peter is suggesting I do, I found this one:
http://cocoadevcentral.com/articles/000075.php
Check it out. It's excellent!

Using the xmpp4r Ruby gem, how can I synchronously discover if a contact is online?

I'm new to XMPP and the xmpp4r library, so please forgive my noob question if this is obviously documented somewhere.
What's the most straightforward way, in a synchronous manner, to find out if a given JID is online? (so that I can call something like is_online?(jid) in an if statement)
My details:
I'm writing a Sinatra app that will attempt to send a message to a user when a particular url gets requested on the web server, but it should only try to send the message to the user if that user is currently online. Figuring out if a given JID is online is my problem.
Now, I know that if I connect and wait a few seconds for all the initial presence probe responses to come back to the Roster helper, then I can inspect any of those presences from my Roster and call #online? on them to get the correct value. But, I don't know when all of the presence updates have been sent, so there's a race condition there and sometimes calling #online? on a presence from my roster will return false if I just haven't received that presence probe response yet.
So, my current thinking is that the most straightforward way to find out if someone is online is to construct a new Presence message of type :probe and send that out to the JID that I'm interested in. Here's how I'm doing it right now:
#jabber is the result of Client::new
#email is the jid I'm interested in polling
def is_online?(jabber, email)
online = false
p = Presence.new
p.set_to(email)
p.set_from(jabber.jid)
p.set_type(:probe)
pres = jabber.send(p) do |returned_presence|
online = returned_presence.nil?
end
return online
end
Now, this works in cases where the user is actually online, but when the user is offline, it looks like the presence probe message that comes back is being caught by some other presence_callback handler that doesn't know what to do with it, and my is_online? function never finishes returning a value.
Can anyone help me by providing a simple example is_online? function that I can call, or point me in the right direction for how I can detect when the roster is done getting all the initial presence updates before I try checking a presence for #online?
As it turns out, there's not a synchronous way to ask for a JID presence. You've just got to ask for what you want, then wait for your response handler to fire when the response arrives.

Resources