capacitor-community / stripe

Stripe Mobile SDK wrapper for Capacitor
MIT License
185 stars 74 forks source link

Web: Close paymentsheet gives console error and cancellation event not sent and invalid cards sending Complete Event #117

Closed ivantchomgue closed 2 years ago

ivantchomgue commented 2 years ago

Describe the bug On the web, when presenting a paymentsheet, if the end-user clicks on the close button it results to a console error. And as the DOM is not cleaned (we see several stripe-payment-sheet tags when closing and reopening), the last event sent seems to be duplicated. We can have the completed event fired twice and sometimes even thrice or more. Additionally when using an invalid test card provided by stripe 4000000000009995 for instance, the presentPayment sends back a complete event instead of a Failed one (the payment actually fails on the stripe server)

ERROR Error: Uncaught (in promise): [object Undefined]
    at w (polyfills.2de6c3c3713ec7b7058a.js:1)
    at polyfills.2de6c3c3713ec7b7058a.js:1
    at polyfills.2de6c3c3713ec7b7058a.js:1
    at e.invoke (polyfills.2de6c3c3713ec7b7058a.js:1)
    at Object.onInvoke (main.6ccd78685919c5fcb4bf.js:1)
    at e.invoke (polyfills.2de6c3c3713ec7b7058a.js:1)
    at t.run (polyfills.2de6c3c3713ec7b7058a.js:1)
    at polyfills.2de6c3c3713ec7b7058a.js:1
    at e.invokeTask (polyfills.2de6c3c3713ec7b7058a.js:1)
    at Object.onInvokeTask (main.6ccd78685919c5fcb4bf.js:1)

To Reproduce Steps to reproduce the behavior:

  1. Go to https://capacitor-community-stripe.netlify.app/tabs/tab1
  2. Click on F12, create payment sheet, close it and check the console to see the error
  3. Verify that no PaymentFlowEventsEnum.Canceled is sent
  4. See error
  5. Go to https://capacitor-community-stripe.netlify.app/tabs/tab1
  6. Click on F12, create payment sheet, present it and enter a test card with insufficient fund eg. 4000000000009995
  7. Verify that no PaymentFlowEventsEnum.Completed is sent instead of PaymentFlowEventsEnum.Failed
  8. See error Expected behavior Closing the payment sheet should not result on error and send Canceled event. Moreover, the stripe-payment-sheet should be removed from the DOM to avoid unexpected behaviors. Failed cards should not send Completed event but Failed event

Screenshots image image image image

Desktop (please complete the following information):

stickypages commented 2 years ago

@ivantchomgue I'm curious to know how you got it to work on web. I setup a "Pay Now" button, which await createsStripePaymentIntent() await createEphemeralKeys() await createPaymentSheet() // Display payment sheet.

I keep getting Missing argument, make sure to call mount() with valid DOM. Mind sharing what your doing? I am using NuxtJS.

Thanks!

ivantchomgue commented 2 years ago

I do like this, sorry do not have a simple straightforward example.

image in my main.ts image image image image

async checkout() {

      if(!this.connectedUser.paymentCustomerId) {
        //create stripe customer account if not existing
        try {
          const customer = await this.http.post<{paymentCustomerId: string}>(environment.dev.serverUrl + '/api/customer', {
            email: this.connectedUser.email,
            name: this.connectedUser.given_name?
            (this.connectedUser.given_name + (this.connectedUser.family_name?` ${this.connectedUser.family_name}`:'')):
            this.connectedUser.family_name || this.connectedUser.nickname,
            id: this.connectedUser.sub
          }).toPromise();
          console.log(customer);
          this.connectedUser.paymentCustomerId = customer.paymentCustomerId;
        } catch(error) {
          console.error(error);
        }
      }

      if (!this.paymentIntent || !this.ephemeralKey) {
        const { paymentIntent, ephemeralKey, customer, error } =  await this.http.post<{
          paymentIntent?: string;
          ephemeralKey?: string;
          customer?: string;
          error?: any;
        }>(environment.dev.serverUrl + '/api/payment-sheet', {
          customer: this.connectedUser.paymentCustomerId,
          amount: this.totalAmount * 100,
          currency: 'eur'
          }).pipe(first()).toPromise(Promise);
          if(error) {
            console.error('payment intent failed', error);
          } else {
            this.paymentIntent= paymentIntent;
            this.ephemeralKey = ephemeralKey;
          }
      }
      if(this.paymentIntent && this.ephemeralKey) {
        await Stripe.createPaymentSheet({
          paymentIntentClientSecret: this.paymentIntent,
          customerEphemeralKeySecret: this.ephemeralKey,
          customerId: this.connectedUser.paymentCustomerId,
          merchantDisplayName: 'kingdame',
          style: 'alwaysDark'
        });
        // https://stripe.com/docs/testing to see test cards to use
        const res = await Stripe.presentPaymentSheet();
        console.log(res);
      }
  }

And in my nodeJS server:

// https://github.com/stripe/stripe-node
app.post('/api/customer', async (req: Request, res: Response) => {
  console.log(Date.now(), 'Entering api/customer');
  try {
    const customer: Stripe.Customer = await stripe.customers.create({
      description: req.body.id || '',
      email: req.body.email,
      name: req.body.name,
    });
    res.json({paymentCustomerId: customer.id});
    const firestore = new FireStore();
    firestore.firestore().doc(`users/${req.body.id}`).update({
      paymentCustomerId: customer.id,
    });
  } catch (error) {
    res.json({error})
  }
  console.log(Date.now(), 'Exiting api/customer');
});

app.post('/api/payment-sheet', async (req: Request, res: Response) => {
  try {
    const ephemeralKey = await stripe.ephemeralKeys.create(
        {customer: req.body.customer}, {apiVersion});
    const paymentIntent = await stripe.paymentIntents.create({
      amount: req.body.amount, // in cents
      currency: req.body.currency || 'eur',
      customer: req.body.customer,
    });
    res.json({
      paymentIntent: paymentIntent.client_secret,
      ephemeralKey: ephemeralKey.secret,
      customer: req.body.customer,
    });
  } catch (error) {
    res.json({error});
  }
});
hideokamoto commented 2 years ago

I think the error may come from dependent libraries issue.

The event of closing modal seems not processing in this library. So we may need to check and fix this library to resolve it. https://github.com/stripe-elements/stripe-elements

But sorry, I'm not sure how to reproduce the issue or resolve this library issue for now...

rdlabo commented 2 years ago

Is this issue resolved? This don't has any update, so will close. If be update, please re-open. Thanks.