stripe / stripe-ios

Stripe iOS SDK
https://stripe.com
MIT License
2.12k stars 981 forks source link

[BUG] NSInvalidArgumentException crash when creating a Payment Method #2044

Closed russellyeo closed 1 year ago

russellyeo commented 2 years ago

Summary

An NSInvalidArgumentException crash occurs after attempting to call createPaymentMethod(with:completion:) using the paymentMethodParams extracted from a STPPaymentCardTextField.

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_TtGCs26_SwiftDeferredNSDictionaryVs11AnyHashableP__$ stp_dictionaryByRemovingNulls]: unrecognized selector sent to instance 0x60000396e730'
terminating with uncaught exception of type NSException

The stack trace shows this line as being the last line to be executed. It appears the stp_dictionaryByRemovingNulls() method cannot be accessed.

I can also see, using a proxy application, that a request is successfully made to POST https://api.stripe.com/v1/payment_methods:

Request

card[cvc]=123&card[exp_month]=10&card[exp_year]=50&card[number]=4242424242424242&payment_user_agent=stripe-ios/23.1.0%3B%20variant.payments-ui%3B%20STPPaymentCardTextField&type=card

Response

{
  "id": "pm_1LzOHkFuY3MxMhL75IafYU2K",
  "object": "payment_method",
  "billing_details": {
    "address": {
      "city": null,
      "country": null,
      "line1": null,
      "line2": null,
      "postal_code": "12345",
      "state": null
    },
    "email": null,
    "name": null,
    "phone": null
  },
  "card": {
    "brand": "visa",
    "checks": {
      "address_line1_check": null,
      "address_postal_code_check": null,
      "cvc_check": null
    },
    "country": "US",
    "exp_month": 10,
    "exp_year": 2025,
    "funding": "credit",
    "generated_from": null,
    "last4": "4242",
    "networks": {
      "available": [
        "visa"
      ],
      "preferred": null
    },
    "three_d_secure_usage": {
      "supported": true
    },
    "wallet": null
  },
  "created": 1667323800,
  "customer": null,
  "livemode": false,
  "type": "card"
}

It appears the issue happens when attempting to decode the response.

Code to reproduce

(Simplified for the purpose of explanation)

// MARK: - Screen

var cardTextField: STPPaymentCardTextField

let paymentMethodParams: STPPaymentMethodParams = cardTextField.paymentMethodParams
service.createPaymentMethod(paymentMethodParams) { result
    // ...
}

// MARK: - Service

let client: STPAPIClient

func createPaymentMethod(
    with paymentMethodParams: STPPaymentMethodParams,
    completion: @escaping (Result<STPPaymentMethod, Error>) -> Void
) {
    client.createPaymentMethod(with: params) { paymentMethod, error in
        // crash happens in Stripe SDK before this callback is called
    }
}

iOS version

iPhone 14 Pro Simulator (iOS 16.0)

Installation method

SPM

SDK version

23.1.0

Other information

In our application, it is a requirement that we create a Payment Method first before using the ID to confirm the payment, rather than passing in the STPPaymentMethodParams to a STPPaymentIntentParams object and confirming the payment in one go.

davidme-stripe commented 2 years ago

Hi @rus64, thanks for filing this!

This is a strange error, and I'm unable to reproduce it locally in a test SPM app. It looks like the compiler might be stripping out our extensions. Can you share your custom build settings? Are you using iXGuard or any other third-party post-processing tools?

I've prepared a fix that might help: Could you point SPM to the davidme/remove-nsdictionary-tech-debt branch and let me know if you still see the issue?

Thanks!

russellyeo commented 2 years ago

Hey @davidme-stripe, thanks for looking into this and getting back to me.

As far as I know we are not using any post-processing tools like iXGuard or similar.

Good news, I tried resolving the library from that branch via SPM and the crash goes away! Looks like your fix solved the issue, nicely done.

Hoping you can include it in the next release?

Thank you

davidme-stripe commented 2 years ago

Great, thanks for the confirmation! We'll land this fix in v23.1.1, which should go out early next week.

davidme-stripe commented 2 years ago

v23.1.1 is out. Let us know if you're still seeing any other crashes or linker issues.

russellyeo commented 2 years ago

Hey @davidme-stripe, thanks for the update. I've pulled 23.1.1 and tested the integration. I can confirm that the card payments as described above are going through as expected, but unfortunately, I've found another crash when attempting to pay with Apple Pay:

libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[StripeCore.STPAPIClient createPaymentMethodWithPayment:completion:]: unrecognized selector sent to instance 0x60000204fde0'
terminating with uncaught exception of type NSException

The code calling it:

import StripeCore
import StripePayments

extension STPAPIClient: StripeAPIClient {

    public func createApplePayPaymentMethod(
        for payment: PKPayment,
        completion: @escaping (Result<DepopPaymentsPaymentMethod, StripeError>) -> Void
    ) {
        createPaymentMethod(with: payment) { (paymentMethod, error) in
            if let paymentMethod = paymentMethod {
                completion(.success(paymentMethod))
            } else if let error = error {
                completion(.failure(.sdkError(error)))
            } else {
                completion(.failure(.unknown))
            }
        }
    }

}
davidme-stripe commented 2 years ago

Hi @rus64, thanks for following up. It does sound like the linker could be stripping out our extensions. I'll dig into Clang's settings and try to reproduce this.

One suggestion: Does adding import StripeApplePay (and adding the StripeApplePay library via SPM) resolve the issue? Never mind, I just realized you're integrating Apple Pay manually.

davidme-stripe commented 2 years ago

@rus64, I'm still unable to reproduce this with all the Clang flags I can find related to Objective-C, symbol stripping, and link-time optimization. Sorry for the continued inconvenience! A few more questions:

russellyeo commented 1 year ago

Hey @davidme-stripe, thanks for the continued support on this, it's very much appreciated.

  1. I am linking the Stripe module (containing the submodules needed) to both the main application target and another smaller target
  1. We are already passing -ObjC here.
  2. I tried -ObjC -all_load as well with no success.
  3. I have emailed you an exported build log.

Thanks

russellyeo commented 1 year ago

Closing this issue as it is now resolved. After further correspondence with @davidme-stripe via email, he helped me to find the root cause and fix the issue (thanks David!).

We are using Tuist to generate Xcode project files for our application, and with this set up it is necessary to pass the "-Objc" flag in the main application target AND any intermediate targets between this and the Stripe library. Without this, the linker will strip Objective-C category methods, leading to the exception described in this issue.

Relevant links