Adyen / adyen-react-native

Adyen React Native
https://docs.adyen.com/checkout
MIT License
44 stars 32 forks source link

The app freezes when a modal is shown after completing an Apple Pay payment #445

Open ChielBruin opened 4 months ago

ChielBruin commented 4 months ago

Describe the bug When I complete an Apple Pay payment, the drop-in closes but the app is completely unresponsive. The payment itself did succeed.

To Reproduce Steps to reproduce the behavior:

  1. Start a payment
  2. Complete the payment using Apple Pay, the drop-in closes
  3. The app is now unresponsive (also the on-success callbacks are never called)

Expected behavior The app should function normally after completing a payment (which in our case would show a pop-up telling the user that the payment succeeded)

Logs I get the following error messages in my logs:

2024-05-21 13:46:49.105899+0200 X[446:34715] [ProcessSuspension] 0x1149b8b40 - ProcessAssertion: Failed to acquire RBS Background assertion 'WebProcess Background Assertion' for process with PID 448, error: Error Domain=RBSAssertionErrorDomain Code=2 "Specified target process does not exist" UserInfo={NSLocalizedFailureReason=Specified target process does not exist}
2024-05-21 13:46:51.077988+0200 X[446:34715] [Presentation] Attempt to present <RCTModalHostViewController: 0x108aa4b30> on <UIViewController: 0x108b17d40> (from <UIViewController: 0x108b17d40>) which is already presenting <Adyen.DropInNavigationController: 0x109957400>.
2024-05-21 13:46:52.215488+0200 X[446:35296] [tcp] tcp_input [C17.1:3] flags=[R] seq=3993052042, ack=0, win=0 state=CLOSED rcv_nxt=3993052042, snd_una=3676108723
2024-05-21 13:46:52.216416+0200 X[446:35296] [tcp] tcp_input [C12.1:3] flags=[R] seq=1152004211, ack=0, win=0 state=CLOSED rcv_nxt=1152004211, snd_una=408956273
2024-05-21 13:47:19.104813+0200 X[446:34715] Could not signal service com.apple.WebKit.WebContent: 113: Could not find specified service
2024-05-21 13:47:19.112128+0200 X[446:34715] Could not signal service com.apple.WebKit.Networking: 113: Could not find specified service

It seems to fail to overwrite the drop-in in the view stack

Devices:

Additional context The shown behaviour is similar to an issue with integrating Apple Pay that I had in a different library, but they are probably unrelated: https://github.com/mkharibalaji/react-native-adyen-payment/pull/54

descorp commented 4 months ago

Hey @ChielBruin

Complete the payment using Apple Pay, the drop-in closes

The app is now unresponsive (also the on-success callbacks are never called)

The expected way to dismiss DropIn/Component UI is to call nativeComponent.hide(success) in successs/failer callback.

Are you using /sessions or /payments flow? Do you receive any callback after click "Pay" in the ApplePay Sheet ?

ChielBruin commented 4 months ago

We use the advanced flow like shown in the README and example app:

class Client {

  public async payments(
    data: PaymentMethodData,
    component: AdyenActionComponent,
    _extra: unknown,
  ): Promise<undefined | PaymentResponse> {
    const body = {
      ...data,
      ...this.config,
      recurringProcessingModel: 'CardOnFile',
      channel: Platform.OS === 'ios' ? 'iOS' : 'Android',
    };

    const response = await this.request(body, '/payments');

    if (response.action) {
      component.handle(response.action);
      return undefined;
    } else {
      this.handleResponse(response, component);
      return response;
    }
  }

  public async paymentsDetails(
    data: PaymentDetailsData,
    component: AdyenActionComponent,
  ): Promise<undefined | PaymentResponse> {
    const response = await this.request(data, '/payments/details');
    this.handleResponse(response, component);

    return response;
  }

  private async request(data: object, path: string): Promise<any> {
    const response = await request
      .agent()
      .post(this.baseUrl + path)
      .set('Content-Type', 'application/json')
      .send(data)
      .ok(() => true);

    if (response.status !== Http.OK) {
      console.error(response.body);
      throw new Error(response.body);
    }

    return response.body;
  }

  private handleResponse(
    response: PaymentResponse,
    component: AdyenActionComponent,
  ): void {
    const success = [
      ResultCode.authorised,
      ResultCode.received,
      ResultCode.pending,
      ResultCode.presentToShopper,
    ].includes(response.resultCode as ResultCode);

    component.hide(success);
    if (!success) {
      throw response;
    }
  }
}
  <AdyenCheckout
        config={client.config}
        paymentMethods={paymentMethods}
        onSubmit={(...args) =>
          client
            .payments(...args)
            .then((response) => {
                // Show success/error message
            }).catch((ex) => {
                // Show error message
            })
        }
        onAdditionalDetails={(...args) =>
          client
            .paymentsDetails(...args)
            .then((response) => {
                // Show success/error message
            }).catch((ex) => {
                // Show error message
            })
        }
        onError={(error, component) => {
          component.hide(false);
          // Show error message
        }}
      >

This works perfectly for iDEAL and creditcard payments, but not for Apple Pay. We do not get any of the callbacks called that I would expect to be called (I even tried the onComplete that is voucher-only according to the docs). It just logs the error I posted at the top about not being able to replace the drop-in with a different screen.

descorp commented 4 months ago

In your code you are missing component.hide(false); in case the request have failed. But overall looks good.

One suggestion - use useCallback. This will make sure only one instance of a function exist.

2024-05-21 13:46:51.077988+0200 X[446:34715] [Presentation] Attempt to present <RCTModalHostViewController: 0x108aa4b30> on <UIViewController: 0x108b17d40> (from <UIViewController: 0x108b17d40>) which is already presenting <Adyen.DropInNavigationController: 0x109957400>.

Seems like something is trying to present RCTModalHostViewController but do not expect to see our DropInNavigationController there 🤔

Are you using any 3rd-party libraries for spinner or loading bar during network call, maybe? I wonder if there is something causing re-rendering "behind the scene" 🤔

Could you make a console.log in here and check if your AdyenCheckout got re-rendered ?

ChielBruin commented 4 months ago

Thanks for spotting the missing call to hide()! But sadly that wasn't the issue and converting to using useCallback didn't solve it either.

I added some logs to see what re-renders are triggered:

I found that I got message 2 and 4 a couple ms before the "Attempt to present"-error

Regarding other libraries:

descorp commented 4 months ago

Is it possible for you to create a blank app and reproduce this behaviour there?

camil-adyen commented 2 months ago

Hey @ChielBruin, do you still face this issue with the latest releases?

ChielBruin commented 1 month ago

I just tried with version 2.2.0 and still see the same issue

Do you know if other user have gotten ApplePay to work (meaning I probably missed some configuration step somewhere)?

descorp commented 1 month ago

Hey @ChielBruin

It is confirmed to work. At least problem described in this ticket seems unique. Mostly, people struggling with setting up Apple Pay.


I just tried with version 2.2.0 and still see the same issue

I believe this could be a "re-render" issue. Something is mutating state of your "parent" view and it causes recreation of AdyenCheckout.

Is it possible for you to create a blank app and reproduce this behaviour there? This would allow us to debug and hopfully find a solution.

ChielBruin commented 1 month ago

I've narrowed down the issue to the popups we show upon payment completion using the react-native-modal library. As soon as I comment out usage of the modal Apple pay works normally, so the issue might be related to this issue with multiple modals. The weird thing however is that displaying this modal for any other payment method does not result in the app freezing.

descorp commented 1 month ago

ApplePay is a bit unique - it is not a standard UIViewController: it is another window, running in a separate thread, appearing above your screen. With other VC, 3rd party library (we do that, for example) can traverse view hierarchy and present itself on top, but it is not possible with Apple Pay.

Maybe this is what causing this collision on iOS level

descorp commented 1 month ago

Hey @ChielBruin

Any luck?

ChielBruin commented 1 month ago

I have not been able to spend more time on it after I figured out what was causing the issue. It might be worth checking what https://github.com/mkharibalaji/react-native-adyen-payment does differently in their Apple Pay implementation, as we did not see the same issue with multiple modals when using that library. But I am not sure when I can spend time on that due to other priorities