capacitor-community / stripe

Stripe Mobile SDK wrapper for Capacitor
https://capacitor-community-stripe.netlify.app/
MIT License
192 stars 77 forks source link

[iOS] Paymentsheet not showing available payment methods #313

Open AndreasKuhlmann opened 11 months ago

AndreasKuhlmann commented 11 months ago

Platform

Describe the bug Paymentsheet not showing all available payment methods

To Reproduce Steps to reproduce the behavior:

  1. Enable multiple payment methods in Stripe (Paypal, credit card etc.)
  2. Present paymentsheet on iOS
  3. Present paymentsheet on Android
  4. compare

Expected behavior Paymentsheet on iOS should show the all available payment methods, like on Android

Screenshots IMG_4416 IMG_4415

Using Function:

miraclemaker commented 11 months ago

I'm seeing this issue too.

larsblumberg commented 10 months ago

@rdlabo availability of PayPal payment option is a core feature for this plugin, on iOS as well. The users of this plugin would be very thankful if you could share any insights on what we could expect from this plugin concerning PayPal on iOS. I am personally motivated to contribute improvements on this matter, however we do need some guidance from you on what you suggest to do here.

AndreasKuhlmann commented 10 months ago

This is what I found out so far:

To show all available payment methods on iOS it is required to initialize the returnURL option of createPaymentSheet({}):

createPaymentSheet( {…, returnURL: “my-app://path-in-your-app”,…} )

This makes sense, because stripe will redirect to external pages (PayPal.com, etc.) to fulfil payment and of cause needs to jump back somehow, when payment went through.

iOS or stripe seems to be here very strict and evaluates the url immediately after assignment (surprisingly not so on Android). If returnURL is not initialized external payment methodes are not shown on the payment sheet. And if you assign any invalid url the app will show an error message on the payment sheet or will crash badly - at least on my device!

But for iOS a valid Url means a Url with a “Custom URI Scheme”: See here for more details: https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app)

At the end of the day, you need add an entry into Info.plist like this:

... <array> <dict> <key>CFBundleTypeRole</key> <string>Viewer</string> <key>CFBundleURLName</key> <!-- your app bundle name here: --> <string>com.example.my-app</string> <key>CFBundleURLSchemes</key> <array> <!—- your app name here: --> <string>my-app</string> </array> </dict> </array> ...

If you now assign “my-app://home/shop/payment-result” to returnURL, hopefully additional available payment methods should now be available and visible.

Of cause home/shop/payment-result must be a valid path in your application as well!

Btw, on redirect stripe adds a whole bunch of parameters as a query string that gives you all information you need to figure out what happened during payment.

Good Luck!

DwieDima commented 5 months ago

Hi @AndreasKuhlmann

Thank you for sharing your insights, it helped a lot.

I have now got to the point where paypal is displayed as a payment method. when adding it, the paypal test setup page also appears (see screenshot).

After I click on "Authorize Test Setup" or "Fail Test Setup", a redirect occurs (URL is logged in Xcode with my.app.link://maps followed by params), but the InAppBrowser never closes and the method Stripe.confirmPaymentFlow() is never resolved. So the process hangs at this point and the payment method is never added.

Did you manage to pay with Paypal? And what do you mean by "Of cause home/shop/payment-result must be a valid path in your application as well!". Do you need to declare it in Xcode? Am I missing something here?

  public async confirmCreditCardSetup(
    setupIntentResponse: SetupIntentResponse
  ) {
    await Stripe.createPaymentFlow({
      setupIntentClientSecret: setupIntentResponse.clientSecret,
      customerId: setupIntentResponse.customerId,
      customerEphemeralKeySecret: setupIntentResponse.ephemeralKey,
      merchantDisplayName: 'My App Name',
      returnURL: 'my.app.link://maps', // maps is a valid url in my app.routing.module
      style: 'alwaysLight'
    });

    await Stripe.presentPaymentFlow();

// the process get stuck here. this Stripe.confirmPaymentFlow() never resolves 🥲    
const { paymentResult } = await Stripe.confirmPaymentFlow();
    if (paymentResult !== PaymentFlowEventsEnum.Completed) {
      return Promise.reject('Payment failed');
    }

    return Promise.resolve();
  }
AndreasKuhlmann commented 5 months ago

Hi @DwieDima, "my.app.link" looks kind of strange to me. Not sure if this is a valid URL Scheme identifier. Try one without dots. The "path" after in "my-app://path" must point to an existing component or navigation target

DwieDima commented 5 months ago

Hi @AndreasKuhlmann

this was not the issue. I finally managed to add PayPal as payment method for future use. For iOS you have to also call Stripe.handleURLCallback({ url }) when you receive the callback from Stripe, so the InAppBrowser closes correctly.

I also used Stripe.createPaymentFlow(), Stripe.presentPaymentFlow() and Stripe.confirmPaymentFlow()

instead of: Stripe.createPaymentSheet() and Stripe.presentPaymentSheet().

my URL SCHEME in Info.plist

    <dict>
            <key>CFBundleTypeRole</key>
            <string>Editor</string>
            <key>CFBundleURLName</key>
            <string>$(BUNDLE_IDENTIFIER)</string>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>accept-a-payment</string>
            </array>
        </dict>
import {
  PaymentSheetEventsEnum,
  Stripe
} from '@capacitor-community/stripe';
import { App } from '@capacitor/app';

// I'm using here the BUNDLE_IDENTIFIER in my Info.plist
const REDIRECT_URL = 'com.company.appName://stripe-redirect'

@Injectable({
  providedIn: 'root'
})
export class PaymentService {

  constructor() {
    Stripe.initialize({
      publishableKey: environment.stripePublishableKey
    });

      // this is needed to complete the process, so the Stripe Browser is closed for returnUrl
      App.addListener('appUrlOpen', async (event) => {
      const url = event.url;
      const isStripeRedirect = url.includes(REDIRECT_URL);

      if (isStripeRedirect) {
        // the url from stripe redirect
        // com.company.appName://stripe-redirect?redirect_status=succeeded&setup_intent=seti_YOUR_SETI&setup_intent_client_secret=seti_YOUR_CLIENT_SECRET
        await Stripe.handleURLCallback({
          url
        });
      }
    });

 public async confirmCreditCardSetup(
    setupIntentResponse: SetupIntentResponse
  ) {
    await Stripe.createPaymentSheet({
      customerId: setupIntentResponse.customerId,
      setupIntentClientSecret: setupIntentResponse.clientSecret,
      customerEphemeralKeySecret: setupIntentResponse.ephemeralKey,
      merchantDisplayName: 'Company Name',
      style: 'alwaysLight',
      returnURL: REDIRECT_URL
    });

    const { paymentResult } = await Stripe.presentPaymentSheet();

    if (paymentResult !== PaymentSheetEventsEnum.Completed) {
      return Promise.reject('Payment failed');
    }

    return Promise.resolve();
  }
  }

result:

https://github.com/capacitor-community/stripe/assets/26873275/bdc2532c-c436-42da-b72e-0f2e16a97b52