Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 error when using createPlatformPayPaymentMethod for Apple Pay on physical devices #1236

Closed
walkerdb opened this issue Dec 7, 2022 · 9 comments · Fixed by #1243
Closed
Labels
question Further information is requested

Comments

@walkerdb
Copy link
Contributor

walkerdb commented Dec 7, 2022

The following code returns a working token in development but in production builds it returns an error saying "The payment has been cancelled":

const cartItems = [{
  paymentType: PlatformPay.PaymentType.Immediate,
  label: 'Company Name',
  amount: '0.01',
  isPending: true,
}];

const { token, error } = await createPlatformPayPaymentMethod({
  applePay: { merchantCountryCode: 'US', currencyCode: 'USD', cartItems },
});

Visually on iOS calling this opens up the apple pay card selection form, then a success sound plays after authenticating with TouchID, then the form closes itself, but the function itself returns an error object whose message field is the above error message.

Are we doing something wrong here? As far as we can tell we've set things up correctly with our merchant ID and publishable key in the StripeProvider, and the exact same values were working when using tipsi-stripe. Would love pointers here if you have any.

Test Devices:

  • iPhone SE 2nd gen, iOS 15.0.1
  • iPhone 13 Pro, iOS 16.0
@walkerdb
Copy link
Contributor Author

walkerdb commented Dec 8, 2022

Update: we are very occasionally also seeing the message "This transaction approval has already been used to create a token." instead, but regardless it's still always failing in prod.

@charliecruzan-stripe
Copy link
Collaborator

Is it possible that you're calling createPlatformPayPaymentMethod twice?

@charliecruzan-stripe charliecruzan-stripe added the question Further information is requested label Dec 8, 2022
@walkerdb
Copy link
Contributor Author

walkerdb commented Dec 8, 2022

@charliecruzan-stripe according to all of our logs we're only calling it once

@charliecruzan-stripe
Copy link
Collaborator

Interesting, well it seems from the message that we're getting here, but at that time applePaymentMethodFlowCanBeCanceled should be false (bc iOS triggers this function first), and the only time it's ever set to true is here at the beginning of the createPlatformPayPaymentMethod method, which is why I ask if there's any chance it's getting triggered twice.

@walkerdb
Copy link
Contributor Author

walkerdb commented Dec 9, 2022

@charliecruzan-stripe

More specifically what seems to be happening is that the first time a user tries to set up apple pay on a physical device through createPlatformPayPaymentMethod, they get

This transaction approval has already been used to create a token.

then each successive time they try to set up the card afterwards they get

The payment has been cancelled

I have no idea where the first message is coming from - it doesn't seem to be from code in this repo or in stripe-ios, nor does it show up anywhere in google except for a link to this issue. I'm assuming then that it must be a stripe backend API response?

Also wondering given the fact that future calls hit the The payment has been cancelled code path if that particular failure mode exits the swift code before applePaymentMethodFlowCanBeCanceled is ever set to false again after it was initially set to true the first time.

@walkerdb
Copy link
Contributor Author

walkerdb commented Dec 11, 2022

Looking at our Stripe logs, it seems that after completing the apple pay form summoned by createPlatformPayPaymentMethod, stripe-react-native is making 3 API calls:

  1. A call to the tokens endpoint (response: 200)
  2. A call to the payment_methods endpoint, using the token from the the first call (response: 200)
  3. A second call to the tokens endpoint (response: 400)

The second token call is what is failing and triggering the error logic. Based on the error message this may be because both token calls are using the same pk_token_transaction_id?

Some request details:

  1. /v1/tokens
    // POST request body
    {
      "pk_token_instrument_name": "Visa 7418",
      "pk_token_payment_network": "PKPaymentNetwork(_rawValue: Visa)",
      "pk_token": "******",
      "payment_user_agent": "stripe-ios/23.3.0; variant.legacy",
      "pk_token_transaction_id": "C3D876BB540E1AD8EB012826A538E2B7D1D3785966E9446B2FF0C4155DC27EBA"
    }
    // response body
    {
      "id": ["tok_1MDeLHCtqB39Ngsoil2SMsAX"],
      "object": "token",
      "card": {
        "id": "card_1MDeLHCtqB39NgsoucCXfJ4I",
        "object": "card",
        "address_city": null,
        "address_country": null,
        "address_line1": null,
        "address_line1_check": null,
        "address_line2": null,
        "address_state": null,
        "address_zip": null,
        "address_zip_check": null,
        "brand": "Visa",
        "country": "US",
        "cvc_check": null,
        "dynamic_last4": "3313",
        "exp_month": 12,
        "exp_year": 2023,
        "funding": "credit",
        "last4": "7418",
        "name": null,
        "tokenization_method": "apple_pay"
      },
      "client_ip": "*redacted*",
      "created": 1670722115,
      "livemode": true,
      "type": "card",
      "used": false
    }
  2. /v1/payment_methods
    // POST request body
    {
      "type": "card",
      "payment_user_agent": "stripe-ios/23.3.0; variant.legacy",
      "card": {
        "token": "tok_1MDeLHCtqB39Ngsoil2SMsAX"
      }
    }
    // response body
    {
      "id": "pm_1MDeLHCtqB39NgsoJEpH8vVd",
      "object": "payment_method",
      "billing_details": {
        "address": {
          "city": null,
          "country": null,
          "line1": null,
          "line2": null,
          "postal_code": null,
          "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": 12,
        "exp_year": 2023,
        "funding": "credit",
        "generated_from": null,
        "last4": "7418",
        "networks": {
          "available": [
            "visa"
          ],
          "preferred": null
        },
        "three_d_secure_usage": {
          "supported": true
        },
        "wallet": {
          "apple_pay": {
          },
          "dynamic_last4": "3313",
          "type": "apple_pay"
        }
      },
      "created": 1670722115,
      "customer": null,
      "livemode": true,
      "type": "card"
    }
  3. /v1/tokens
    // POST request body
    {
      "pk_token_payment_network": "PKPaymentNetwork(_rawValue: Visa)",
      "pk_token": "******",
      "pk_token_transaction_id": "C3D876BB540E1AD8EB012826A538E2B7D1D3785966E9446B2FF0C4155DC27EBA",
      "payment_user_agent": "stripe-ios/23.3.0; variant.legacy",
      "muid": "74587f94-3cd7-4db4-ac54-0d376e02ebdce4fae0",
      "pk_token_instrument_name": "Visa 7418",
      "guid": "03cd52c4-50f7-45f7-87fb-8dec62975bd72a97bf",
      "sid": "3992de73-3f1a-4d00-bc16-381f7dcd54a7c87cad"
    }
    {
      "error": {
        "message": "This transaction approval has already been used to create a token.",
        "request_log_url": "https://dashboard.stripe.com/logs/req_tqZALcJ0ZYryn7?t=1670722115",
        "type": "invalid_request_error"
      }
    }

@walkerdb
Copy link
Contributor Author

walkerdb commented Dec 11, 2022

Ok yeah so it looks like this is where the issue is:

STPAPIClient.shared.createPaymentMethod(with: payment) { paymentMethod, error in
if let error = error {
self.createPlatformPayPaymentMethodResolver?(Errors.createError(ErrorType.Failed, error))
} else {
STPAPIClient.shared.createToken(with: payment) { token, error in
if let error = error {
self.createPlatformPayPaymentMethodResolver?(Errors.createError(ErrorType.Failed, error))
} else {
var promiseResult = ["paymentMethod": Mappers.mapFromPaymentMethod(paymentMethod?.splitApplePayAddressByNewline()) ?? [:]]
if let token = token {
promiseResult["token"] = Mappers.mapFromToken(token: token.splitApplePayAddressByNewline())
}
self.createPlatformPayPaymentMethodResolver?(promiseResult)
}
}
}
completion(PKPaymentAuthorizationResult.init(status: .success, errors: nil))
}

tl;dr createPlatformPayPaymentMethod first calls stripe-ios's STPAPIClient.shared.createPaymentMethod method, which internally seems to be using and consuming the one Token opportunity Stripe allows us for this Apple Pay transaction, making the later call to STPAPIClient.shared.createToken fail.

  1. First we call STPAPIClient.shared.createPaymentMethod, which uses the token API internally to create the payment method. This token call uses the pk_token_transaction_id field, which as far as I can tell is taken straight from the Apple SKPaymentTransaction object's transactionIdentifier field.
  2. then we're calling STPAPIClient.shared.createToken again, passing in the payment method that STPAPIClient.shared.createPaymentMethod returned. Since createToken uses the pk_token_transaction_id field from the payment method you pass it, it's a second call to the tokens endpoint with the same Apple Pay transaction ID, which the stripe tokens API seems to guard against.

this line in stripe-ios may be why it's working in sim just fine - it uses a test transaction ID every time in sim, but uses a real pk_token_transaction_id on the PKPayment object otherwise.

I'm.. not really sure where to go from here tbh. Is there a way to directly call STPAPIClient.shared.createToken from stripe-react-native with the filled-out Apple Pay form params without also having a side-effect of creating a paymentMethod? (since calling that method prevents a new token from being created)

@walkerdb walkerdb changed the title 🐛 getting "The payment has been canceled" error when using createPlatformPayPaymentMethod 🐛 getting error when using createPlatformPayPaymentMethod for Apple Pay on physical devices Dec 13, 2022
@walkerdb walkerdb changed the title 🐛 getting error when using createPlatformPayPaymentMethod for Apple Pay on physical devices 🐛 error when using createPlatformPayPaymentMethod for Apple Pay on physical devices Dec 13, 2022
@charliecruzan-stripe
Copy link
Collaborator

the best long-term solution here may just be to move out the token creation to a separate method, and so instead we'd have:

  • createPlatformPayPaymentMethod
  • createPlatformPayToken

where each one accepts the exact same params, and migrating from the latter to the former is trivial. There doesn't seem to be a situation where one needs both the PaymentMethod & a Token.

@charliecruzan-stripe
Copy link
Collaborator

Fix was released in v0.23.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants