If you're developing a mobile app that takes payments, you may have to use a "Hosted Payment Page". sometimes it just fails to return to your app. This might be why.

This'll take a bit of explanation, so we'll try to break it down.

Problem Description.

You're developing an app, which has a purchase function. You need to use a hosted payment page, and in some cases you get feedback that the app is just hanging.

What's a 'hosted payment page'?

Allowing a user to enter their credit card details adds a whole new world of pain, as it takes you into the realm of PCI-DSS; The security standard for card acceptance. So, rather than having to build all the necessary infrastructure, you can rely on a payment provider's "Hosted Payment Page". This is a web page, that is served by your payment provider not yourself, that is responsible for allowing a user to enter credit card details to pay for a good or service. The payment provider deals with all the necessary security associated with PCI-DSS; Think of it as outsourcing your payment woes.

But, why don't I just use Apple Pay/Google Pay?

In an ideal world, this is exactly what your should do. But, your client may have other plans. They may have surveyed their user base and found that a proportion have credit or debit cards which aren't supported by Apple/Google pay, and therefore we are required to provide support for their customers to enter their credit card details.

OK, I have to use a hosted payment page, but it doesn't always return.

So here's what's going on there:

  1. When you open a browser to a hosted payment page you typically supply a "return URL". This is the last address in your payment journey which the browser will visit, typically back at your site. This will be visited, passing parameters to you to let you know if the payment was a success or failure. Think of it like this: You're instructing the browser ("Please go to my hosted payment page, and get paid. When it is done send the user back to me at this return URL").
  2. All seems to go fine.
  3. Some users say "it never completed".

This one had us scratching our heads. So we dug. And we dug. We initially saw this with a single client, and only on Android. Many months later, it raised its head again, this time on iOS. And of course we had the whole "we didn't change anything, what did you change in the app?" question. (spoiler: nothing)

So here's what's happening:

  1. We supplied a custom URL to end the journey. The custom URL was unique to our app. Think of it as myapp://paymentcomplete (rather than a https link)
  2. Most of the time, the journey completed, the browser attempted to redirect to myapp://paymentcomplete; this signalled the app, and the app says "hey, that payment is complete, it is safe to dismiss the payment journey".
  3. But sometimes, we never got the signal.

Why not?

Well, what we did find is it was more likely to happen after a 3D secure step up challenge. This is where during the payment journey, you are brought off to your bank to respond to a push notification, or enter a OTP you received on SMS or some such.

This leads to a number of redirections along the way

  • the user goes to the hosted payment page and fills in their details. They press submit.
  • The payment provider goes off and gets told "take them to their bank".
  • So they issue a redirect to your bank; your bank may outsource this.
  • So there may be a further redirect to that. Now, you interact with the page (enter your OTP, or whatever), and
  • ..there's a collection of redirections to get you back from whence you came, culminating in the redirection to your custom URL (myapp://paymentcomplete remember?)
  • ...and this redirect is not followed.

As best we can make out - this is an ad fraud prevention mechanism. The browser has seen a number of redirections in a row, all of which have happened without user interaction, and at some point it says "hey, this is doing a few too many redirects for my liking, I'm not gonna go any further until the user tells me to"

Ad fraud?

Yeah. Imagine you make money off ad impressions. If you can redirect a user to a page that quickly loads an ad, then redirects you to the page they were intending to go to, you can rack up ad revenues without the user ever seeing the ad; multiple redirects can be used in theory to do this.

The browser likes interaction; its not crazy about blindly following lots of automatic redirects.

What about my well-behaved payment journey, with a hosted payment page, a proper 3D step up challenge?

In theory it should just work. In practise it sometimes doesn't - and you cannot control it. Our guess is this is decided based on the number of redirections in a short period of time and this number and time period are not documented (because the ad fraudsters would use this to their advantage). When you send a user to your hosted payment page - you don't know where that will take them. They may bank anywhere, and their bank may require more steps to redirect back than others.

There are tons of ways of doing redirection - do they all fail?

I'm afraid so. We tried:

  • Server side redirect; including all of the various 3xx response codes.
  • Meta refresh
  • Body onload JavaScript - calling both window.location.href and window.location.replace
  • We've tried adding delays
  • We've tried loading the final redirect URL in a page as an image, as a script, and using AJAX calls.
  • We've tried toying with the content security policy on the page. This is an interesting one; safari doesn't allow non http or https protocols in the content-security-policy directive, despite the standards saying this is ok

Is there a solution?

Well, we haven't found an elegant one yet. What we do know is if you introduce user-interaction, it will always work. So, if your next-to-final redirect is a page that says "Payment Complete, please press the button below to dismiss", and this button triggers a redirect, this will always work. It isn't as slick as the payment journey auto dismissing that we'd like.

Of course, we can do both. We have the page with the button, along with some JavaScript to redirect on load. If it is going to work, the auto dismiss will work; if the user interaction is required, the user can press the button. Not ideal, but at least only affected users get the lesser experience.

We've reported this to the Safari Team at Apple, if you want to read on, you can at this link


Thanks for reading the Tapadoo blog. We've been building iOS and Android Apps since 2009. If your business needs an App, or you want advice on anything mobile, please get in touch