5

Working on a website that has been integrated with Apple Pay for about a year now with no issues. We are noticing that we are always getting an error now for iOS 13 devices only. iOS 12 and under all work as expected.

Front end click handler:

<div class="btn btn-apple">
  <div class="apple-pay-button apple-pay-button-white" lang="en" data-bind="click: intializeApplePaySession"></div>
</div>

JS click handler:

self.intializeApplePaySession = function() {
  new RestClient(RestClient.POST, "/rest/model/someController", {},
    //Success
    function(data, textStatus, jqXHR) {

      var session = new ApplePaySession(3,data.applePayPaymentRequest);
      initializeCallbacks(session);
      session.begin();
    },
...

"Must create a new ApplePaySession from a user gesture handler." is always thrown at

var session = new ApplePaySession(3,data.applePayPaymentRequest);

for all iOS 13 devices. iOS 12 and below work fine with the same code.

From my debugging I can see that the event being handled is a MouseEvent which makes sense to me. Any ideas as to why this is being thrown?

Paul Hoke
  • 115
  • 2
  • 7

4 Answers4

5

We are evolving away from letting websites make cross origin calls without the user's input.

One of the ways this has been locked down for a while is that you can't fire, for instance, a submit button for a form... unless you do it from a click() handler. So you can put a big shiny "Buy now" image on the page and submit the form from clicking on that image, but you can't just do a setTimeout(form.submit(), 100)

I think you've got the same class of thing going here. The REST call is in the click handler, but the apple pay call is in the callback to the REST call, and therefore not really in the click() handler.

Of course you're using the REST call to generate the data to pass to Apple. You may have to generate that data speculatively. But before doing that infrastructure work, I'd experiment with whether an async version of your click handler passes their sanity checks.

Jason
  • 3,021
  • 1
  • 23
  • 25
  • Spot on. Looking at that click handler, you could probably get away with initializing the RestClient as soon the viewmodel loads (and save the data it returns), since the handler doesn't seem to pass any purchase-related data to the RestClient anyway. Then the code that is now in the success callback would become `initializeApplePaySession`. – Brother Woodrow Oct 05 '19 at 09:54
  • Thanks both Jason and Brother Woodrow, this actually makes sense. I can definitely decouple the Rest call from the creation of the Apple Pay session. I will try this out. – Paul Hoke Oct 05 '19 at 21:04
1

Working snippet following discussion with Jason and Brother Woodrow

Synchronous function to call backend and get current cart details. The reason we previously had this in the success callback is because we are using a SPA framework so the cart can change at any time. We cannot call this when the page loads or the data may be stale.

self.getApplePayData = function() {
  var self = this;
  new RestClient(RestClient.POST, "/rest/model/getApplePayPaymentRequest", {},
    function(data, textStatus, jqXHR) {
      self.applePayData = data;
    },
....

 self.intializeApplePaySession = function() {
  var self = this;
  self.getApplePayData();
  var session = new ApplePaySession(3,self.applePayData.applePayPaymentRequest);

I think the key issue is that we were creating a new ApplePaySession from within the success callback of the rest call which Apple does not seem to want to be possible anymore.

Paul Hoke
  • 115
  • 2
  • 7
1

Reference from the official documentation

You attempt to create the ApplePaySession outside of a user gesture handler. The exception error "Must create a new ApplePaySession from a user gesture handler" appears.

So in your case, you must create an apple pay session like

session = new window.ApplePaySession(version, request);

then create a request to the server and receive a response.

const data = async getSomeCheckoutData();

and you can proceed with payment throw apple pay

...
session.begin();

The main idea, it's, first of all, create a session. Then you can do requests and after it start apple pay session.

Yevhenii
  • 301
  • 1
  • 5
  • 14
  • Best Answers which actually worked as variables like `session`, `request` all needs to be created in the click handler method itself. – vibs2006 Feb 02 '22 at 11:34
1

You must declare ApplePayInstance variable above class, to trick apple pay session, so that it consider it as gesture, Also don't call in promises

http://baiduhix.blogspot.com/2018/06/must-create-new-applepaysession-from.html