stripe-react-native icon indicating copy to clipboard operation
stripe-react-native copied to clipboard

[ios] Close Apple Pay sheet programmatically

Open jaaywags opened this issue 4 years ago • 12 comments

Is your feature request related to a problem? Please describe. I don't think so.

Describe the solution you'd like I would like to be able to call theuseApplePay()method, and it return a method which can be used to close the Apply Pay window.

Currently, it returns 3 methods, and one property. Can we add a 4th method? Maybe called closeApplePayWindow()

Additional context I have an application where the user can toggle between n subscription tiers. They can pay by entering their card info into the app, or choose to pay with Apple Pay.

If they choose to pay with Apple Pay, the window pops up asking which card they would like to use. Once they decide and select their card, the application sends a request to our Payment Service, with the payment id they selected. Our Payment Service will subscribe the customer to that subscription plan using the payment method. The Payment Service will return success if everything goes well.

The application is then done processing the payment. It updates the state as needed, but there is no way to close the Apple Pay window. This is normally done with the confirmApplePayPayment() function, but in our case, we did not use a PaymentIntent, and so we have no ClientSecret to pass it.

Describe alternatives you've considered The existing implementation outlined in the example project utilizes the PaymentIntent workflow. That is not ideal for the subscription model. If I try to create a PaymentIntent, there is no way I can specify what Subscription it corresponds to. This is a problem because once the PaymentIntent succeeds, there is no way our Payment Service can determine this and provision the correct features.

A work around would be to modify our Payment Service to store the PaymentIntent Id, and what Subscription it corresponds to. We could then setup a webhook that looks this up after the PaymentIntent succeeds, but this is (to me at least) not the cleanest way. I think it would be better to implement a method returned from useApplePay(). I can call this method and it will close the Apple Pay window.

jaaywags avatar Jul 21 '21 18:07 jaaywags

I would be willing to make the code change, I just get a bit lost on how to go from stripe-react-native to the StripeSdk. I followed the call all the way down to here, but I am not sure where to go from there.

jaaywags avatar Jul 21 '21 18:07 jaaywags

You will need to extract the PaymentIntent from your subscription object, return that PaymentIntent's client_secret to your app and then call confirmApplePayPayment with that client_secret. The SDK will read out the state from the PaymentIntent (e.g. succeeded) and close the sheet accordingly.

thorsten-stripe avatar Jul 22 '21 05:07 thorsten-stripe

@thorsten-stripe Hi, thank you for getting back to me.

Creating a subscription does not always create a PaymentIntent. For example, if a customer has credit, that credit is applied to the new cost and if it covers the full amount, no additional payment is required, thus no PaymentIntent is created.

I have an example of this scenario. I attached a screenshot from my debugger. It is a very straight forward approach to create subscription and it is not creating any PaymentIntent. I confirmed this is a valid scenario with the Stripe support team today.

What do you advise we do?

Screenshot

jaaywags avatar Jul 22 '21 19:07 jaaywags

I've got another use case for this feature... I am confirming all my payments server side so I need to be able to call the completion block myself when I get confirmation from the server.

Edit: Forgot to mention that currently there is no way neither of programatically cancelling apple pay. Example would be: present apple pay, confirm payment, something goes wrong, UI cannot recover

acatalina avatar Jul 30 '21 13:07 acatalina

This is definitely a requirement of this SDK. What happens if our call to get a client secret from the server fails? Currently, we have to wait for the Apple Pay UI to timeout itself and disappear, we really need a way to programmatically dismiss it!

simonmitchell avatar Aug 09 '21 09:08 simonmitchell

+1 on this. Another use case is Stripe Connect. You can't currently use Direct Charges in the stripe-react-native API - but if you could cleanly close the Apple Pay Window without a Client Secret, then you could attach the payment method to your platform and charge the card server side.

Without this little feature it's actually impossible to use with Direct Charges on Stripe Connect, but with this feature it would be trivial.

jamiembrown avatar Sep 02 '21 22:09 jamiembrown

We have the same issue, we're not able to create subscriptions on stripe!

shqear93 avatar Jan 13 '22 12:01 shqear93

Our case was:

  1. we set a trial period on subscription creation
  2. we want to use apple pay
  3. we can't create $0 payment intent
  4. we can't collect the payment method from apple pay
  5. so we were thinking in a workaround: getting payment method id then dismissing apple payment sheet if the user was eligible for trial period but we couldn't dismiss it

I was able to fix the issue using setup intents, so basically we create a subscription with trial free period in incomplete status (payment_behaviour = default_incomplete and default_payment_method = nil), the newly created subscription will automatically create setup intent in (subscription.pending_setup_intent) where I'm able to retrieve the setup intent by it's id and return client_secret for the app side we followed this article to integrate confirm flow for the setup intent that created by the new subscription

shqear93 avatar Jan 14 '22 12:01 shqear93

This is definitely a requirement of this SDK. What happens if our call to get a client secret from the server fails? Currently, we have to wait for the Apple Pay UI to timeout itself and disappear, we really need a way to programmatically dismiss it!

Is there a workaround for this? Even forcing an error state would be good (I've tried via calling confirmApplePayPayment with a dummy clientSecret, but that just... crashes the app)

JacobJaffe avatar Mar 16 '22 20:03 JacobJaffe

Also having this issue. Creating a subscription using the payment method id retrieved from Apple Pay button and there's no way to close the sheet after this happens without calling confirmApplePayPayment which makes another payment so that the user is charged twice.

innergap avatar Mar 30 '22 15:03 innergap

We're also having this issue but using a hack to get around it.

On the stripe dashboard we create a Payment on Live/Test and record the client secrets generated. We then pass these known good client secrets to confirmApplePayPayment, this works / closes the popup with a done/success message.

The client secrets don't seem to expire / can be confirmed indefinitely from our experience but this could change/may not be the case so its best to use this hack with the client secrets being passed from your server so you can hot swap them if needed.

This appears to work for Stripe Connect as well, we created a payment on the Platform account and used its client secret to close the popup. We are using this for Stripe Connect when creating Shared/Cloned Customers so it may not work in other scenarios

Ideally though an actual fix for this is added as the above could break at any time

KeithM23 avatar Jun 30 '22 16:06 KeithM23

Thanks, @KeithM23 for the work around. Worked for me on iPhone, but didn't on mac Safari.

To successfully close the sheet and create a subscription with stripe, I pass in the client secret from the backend:

  1. subscription = stripe.subscriptions.create
  2. latest_invoice = subscription.latest_invoice
  3. invoice = await stripe.invoices.retrieve(latest_invoice);
  4. payment_intent = invoice.payment_intent
  5. paymentIntent = stripe.paymentIntents.retrieve(payment_intent);
  6. client_secret = paymentIntent.client_secret;
  7. front-end confirmApplePayPayment(client_secret)

However, when I create a subscription with a coupon or trial days, it doesn't create latest_invoice and alas, I cannot use it to retrieve any payment intent client secret - and so I cannot use this method to close the sheet.

update I erroneously thought that Safari on mac actually uses the .ios file instead of the .web file. The method that @KeithM23 provided works well for me on the iPhone! For web, ev.complete('success'); works well to close the sheet.

innergap avatar Jul 14 '22 16:07 innergap

@charliecruzan-stripe how does #1164 close this?

ronak-safara avatar Jan 31 '23 04:01 ronak-safara

With the dismissPlatformPay function

charliecruzan-stripe avatar Jan 31 '23 17:01 charliecruzan-stripe

@charliecruzan-stripe got it thank you. Any chance there's an equivalent in the iOS native library (sorry I thought this was that initially)?

ronak-safara avatar Feb 05 '23 05:02 ronak-safara

This is the native call that that JS function results in -> https://github.com/stripe/stripe-react-native/blob/master/ios/ApplePayViewController.swift#L59 (hopefully that answers your question)

charliecruzan-stripe avatar Feb 06 '23 17:02 charliecruzan-stripe