iTunes In App Purchases and the User Dialog - iphone

My iPhone app includes a subscription service for a web server, which Apple insists that I make available as an in-app purchase. However I not entirely happy about the way the user dialog pans out. Some time after the app submits the purchase to iTunes, the user gets a confirmation request from iTunes. But this could happen at any time after the purchase is submitted.
I am wondering whether my app should pop up an alert, after the submission is made. Is there a standard procedure for this? If the confirmation request comes back straight away, then the user would see two alerts one after the other, which could be confusing. On the other hand, if he is going to have to wait a while, it would be nice to let him know that this is the case.
Is there a standard procedure for this?

I think standard procedure would be to disable user interaction and show a spinner, or otherwise let the user know that the transaction is pending. You can then handle the purchase if it comes back, or implement a timeout and then show a pop-up notifying the user that the transaction could not be completed.
If you really don't want to make the user wait for completion before continuing in the app, I would put your notifying pop-up before you send the request. So when the user presses the button, you pop-up and say "This may take a while", and when they press "OK", THEN you send the request. Personally I don't think this is particularly good UX though, since in the majority of cases it will not take very long.

Related

actions on google - how to handle long running operations

I have AoG action that is logging-in to external backend and once logged in it can control specific appliance via external backend's API. Action basically controls home alarm via commands like arm section XY, disarm section garage, etc. Before getting control to alarm it is necessary to login and this takes considerable time (approx. 20-30 seconds). This is much longer than AoG actually allows resulting in timeouts. I am initiating login as asynchronous operation in actions.intent.MAIN handler (i.e. not waiting for the result of login within handler) and just saying to user to tell the command (arm/disarm garage, etc.) in couple of seconds. I have also implemented push notification which is working fine. Problem with push notification is that it just pops up on mobile phone without any sound & user has to open notifications and tap it. Then it will trigger intent and do requested action.
This is not really good user experience (typically I would like to use my action in the car when coming home and having the possibility to disarm the home alarm without need to touch the phone, tap the notification, etc).
Any idea how to implement it in more proper way? What I would really appreciate is if google assistant could actually re-initiate the conversation & tell me something like: 'hey I am already logged in into alarm service provider, what do you want me to do now?'.
I will be grateful for any advice dealing with similar problem.
I am using ActionsSDK for Node.JS to build my action.
You've already looked at the ways that the Assistant can initiate (or re-initiate) a conversation. Actions are really designed for something that is conversational, and a 30 second pause in the conversation would be awkward.
One other option you have is to use a Media Response as part of your reply to the user logging in (or as part of your welcome intent? Not entirely clear, but the approach would be the same). This would let you play some "hold music" for several seconds. At the end of the music playing, an actions.intent.MEDIA_STATUS would be sent to your Action, which you can use to make sure the login has completed and, if so, respond to the user appropriately.
The only way for AoG to "take initiative of starting a conversation" is through push notifications. There is no way for the assistant to strike up a conversation after a period of time or when an event occur.
Perhaps another way of doing might be to only send push notification if your action fails to execute the long sequence of events and the triggering action could invoke an intent to try again. The assumption would be that everything's fine unless said so.
You could also notify the user that it'll take a couple of seconds to complete the action once it's initated and implement followup intents that handles if the user asks "Is it done?" or "How's it going?". Making it part of the flow to check on progress, but with the assumption that it should be successful.
You can easily dislocate the long running background process by implementing a task queue in Firebase where your intent is creating a child similar to this.
firebase.database().ref("tasks").push({action: "disarm_garage"});
And then you create a cloud function trigger to handle it
functions.database.ref('tasks/{id}').onCreate((snap) => {
const action = snap.val().action;
switch (action) {
case 'disarm_garage':
// ...
break;
}
// Remove the task after processing
return snap.ref.remove();
});
That would ensure that you have enough time to complete the task in background without blocking the conversation.

Implementing PayPal discounts - what if the user cancels?

Alright, I got my Paypal shopping cart set up and working, now I'd like to be able to add discount coupons.
I know I can simply implement the 'discount_amount_cart' variable in my form, but I'm still a little lost when it comes to handling the deactivation of a coupon code.
Let's say A activates the code. Now in my database it will be marked as 'reserved', meaning no one else can use it. Once A completes his transaction, the IPN will mark the code as 'used' and it becomes useless.
But what if A decides to activate the code, but never actually buys anything on my website?
I'm using the $_SESSION variable to save my shopping cart, so once the user returns to my website, the code might still be shown as activated for him. When exactly would I mark it back as 'not reserved & not used' in my database?
I could do this based on time, but this doesn't really feel safe either. Let's say the user puts everything into his basket, goes to Paypal, waits for an hour and then decides to finish his checkout. The discount would still be activated, but in the meantime, it could've already been 'unreserved' in the database and used by another person!?
How would I go about this?
Thanks in advance!
IMHO, this has little to do with PayPal and more about your business rules. If your coupon is single use (globally), then you will be faced with the same issue regardless of any payment flow.
Simply putting it in "cart" and/or any event where a user "activates" said coupon, then just navigating elsewhere in your site, doesn't do anything, [whatever], creates that lag
it could be Paypal, Amazon, your own gateway - it really doesn't matter...but there will be a lag between activation and payment (beyond your control)
I think it's just best to be explicit with your customers and handle it appropriately technically (based on what that explicit messaging is).
You'll see some implementation of "timed purchase" at ticketing sites (e.g. buying a ticket to a game, movie, etc.) - they will have a "timer" for the user to do something (otherwise, the "reservation" is lost).
Hth...
Put an expiry date on coupon use - such as must be used within 10 days of activation. It gives the user urgency to use the coupon and gives you a timeframe to invalidate it.

Multitasking and SKPaymentQueue I get a "stuck" SKPaymentTransaction with a transactionState of SKPaymentTransactionStatePurchasing

My question is basically how to reproduce the behavior I see in other apps in the app store with regards to in-app purchases:
Immediately following the user pressing "Buy" the button is hidden and a spinner is displayed (essentially de-bouncing the Buy action).
The user pressing cancel to the StoreKit dialog (in the app or outside of it) results in the spinner going away.
I'm attempting to show and hide the spinner based on observing the state of transactions in the SKPaymentQueue.
My problem is described by this poster:
In App Purchase user cancels tx while app in background: tx state stays on purchasing
I see the same behavior as the post above regardless of if the test user is logged in first or not. The transaction is basically "stuck" in the queue with a purchasing state forever (meaning my spinner is displayed forever). When the app is completely exited and restarted (not just multi-tasked away) the transaction is no longer in the payment queue so it's clearly not really still purchasing. It's like the SKPaymentQueue "missed" the state change.
Some more detail that I've noticed is that in the typical case, immediately after adding the payment, the transaction shows up in the queue with a purchasing state. If the user presses cancel, the state of the transaction goes to failed.
In the case of tasking away immediately after adding the payment, the transaction does not show up in the queue until tasking back to the app. Instead of seeing two calls to the updatedTransactions delegate (one with a state of purchasing and one with a state of failed) there is only one call to the updatedTransactions delegate with a state of purchasing. The transaction never goes to failed.
Very occasionally, when tasking back to the app I will get a transaction removed callback (after seeing the updatedTransaction with a state of purchasing). Even in this case I never see the expected update of the transaction to the failed state.
I am not able to reproduce this behavior in apps in the app store (they always correctly show/hide the spinner regardless of tasking away or not), but it's not clear to me if that's because they are doing something tricky, or if it's only a problem in the sandbox.
How to replicate this behavior if not by monitoring the state of the transaction? Is this not a problem in production?
Thanks!
I have used MKStoreKit for all my apps.
It has a block based interface that tells you when the transaction has started (To hide the button and start animating).
It will also tell you if the transaction fails or it was competed, so you can stop the animation and restore the button (if it failed) or show something else when it is completed.
Because it uses blocks, you can handle all this behavior without writing a lot of boilerplate code.

How to test Auto Renewal In App Purchase Model

I have to implement In App Purchase Auto-Renewal Model in my app. I have created a test user and getting the response from App Store for subscription. When I tap "Confirm" then sometimes its state becomes SKPaymentTransactionStatePurchased and sometimes it becomes SKPaymentTransactionStateRestored. My problem is that I want to prevent user to use the app if he has not subscribed the app. For that I am trying to track the state of "SKPaymentTransaction" so that I can allow or disallow the user to use the app. Such that, if the state is SKPaymentTransactionStatePurchased, only then he could be able to use the app, but I don't understand how its state changes.
One more thing, When I tap on the "Settings" to manage my Auto-Renewal, it shows me that my app will be expired on the current date i.e., today itself, but when I tapped on the "Confirm" button to subscribe my app for one month then expiry date should be after one month. I don't understand how this whole process is working.
I have read whole documentation of Auto Renewal, but didn't get any help from that.
Please help!
Not sure about your first problem. In my case, I can never get SKPaymentTransactionStateRestored called at all. I am debugging why.
Meanwhile, I was facing the second problem you described. When I decoded the receipt, it looks like they keep the subscription valid for 5 mins. Sounds like a feature for testing in sandbox. At least that's what I am assuming.

SKPaymentTransactionObserver not getting a callback on app switch

I have in-app purchases set up so that when a user clicks on the buy button, a "Please Wait" view appears and adds a payment to the payment queue. This always causes an immediate callback of the queue's observer saying that the transaction entered the "Purchasing" state.
My problem happens when the user returns to the main screen before the confirm to purchase dialog pops up, then cancels. If I wait around on the main screen awhile before switching back to the application, the application never gets any callback about the transaction switching state or being removed. If I examine the payment queue, I can see that the transaction is still in the queue, still in the "Purchasing" state. Is there anything I can do to make it call back and remove it from the queue after resuming the application?
If I understood your situation correctly, you've encountered this problem:
In App Purchase user cancels tx while app in background: tx state stays on purchasing
This appears to be a bug in Apple's Store kit. The issue can easily be reproduced if you send your app to background right after initiating a purchase and then pressing the cancel button when the buy confirmation popup is prompted. Your app will never receive the transaction failed(cancelled) notification and will enter a blocked state (if you disabled the UI trying to prevent the user from interacting with the app until the purchase has finished).
We have to wait for a fix from Apple. In the meantime, if your app is entering a blocked state because you disable UI while making a purchase; you should implement some mechanism to allow the user to leave that state.
Good luck!
I have the same problem, however, I have only managed to reproduce it when using a sandbox appstore account.
Has anyone managed to reproduce it using a real apple account?
I've not tried this yet, but I am thinking about removing my SKPaymentTransactionObserver the moment the app loses focus, and the adding it again when it next becomes active.
The weird thing about about this bug is that you get stuck with a transaction in the purchasing state, however when you properly shutdown the app and the restart it you would expect that transaction in the purchasing state to still be there, however it is not... As if the re-adding the SKPaymentTransactionObserver has triggered it to properly re-evaluate it's state.