#alexzada-3ds-subscriptions

1 messages ยท Page 1 of 1 (latest)

mild shadow
#

Do those help?

exotic ermine
#

so, what is happening here is the following, i create the subscription and return to the front the client_secret, then i pass the client_secret to handleCardAction and when finishing the authentication flow, instead of the paymentInten status is requires_confirmation it already comes as Succeeded and I can't re-call to the backend again to confirm

#

-------------------BACKEND

`const customer = await stripe.customers.create({
description: 'Test Customer',
email: 'test@costumer.com'
});

let pm = await stripe.paymentMethods.attach(request.body.paymentMethodId, {customer: customer.id});

const product = await stripe.products.create({
  name: 'Delivery',
})

const price = await stripe.prices.create({
  unit_amount: 100000,
  currency: 'brl',
  product: product.id,
  recurring: {interval: 'month'},
});

const subscription = await stripe.subscriptions.create({
  customer: customer.id,
  default_payment_method: pm.id,
  items: [
    {price: price.id},
  ],
  expand: ['latest_invoice.payment_intent'],
});

return response.send({
  clientSecret: subscription.latest_invoice.payment_intent.client_secret,
  status: subscription.latest_invoice.payment_intent.status,
});`
#

----------------------FRONTEND

`const handlePay = async () => {
setLoading(true)

const { paymentMethod, error: createPaymentMethodError } = await createPaymentMethod({
  type: 'Card',
})

if (createPaymentMethodError) {
  Alert.alert('Error', 'Houve um erro ao executar o pagamento. Por favor, tente novamente.')
  return
}

await createPaymentIntent({
  createPaymentIntentParams: {
    paymentMethodId: paymentMethod.id,
  },
  onSuccess: async (data) => {
    const { error: createIntentPaymentError, status, clientSecret } = data

    if (createIntentPaymentError) {
      Alert.alert('Error', 'Houve um erro ao executar o pagamento. Por favor, tente novamente.')
      return
    }

    if (status === 'active') {
      Alert.alert('Success', 'Pagamento efetuado com sucesso!')
    }
  
    if (status === 'requires_action') {
      await handleNextActions(clientSecret)
    }
  },
  onError: () => console.log('error'),
})

setLoading(false)

}`

#

`const handleNextActions = async (paymentIntentClientSecret: string) => {
const { error: requiresActionError, paymentIntent } = await handleCardAction(paymentIntentClientSecret)

if (requiresActionError) {
  Alert.alert(`Error code: ${requiresActionError.code}`, requiresActionError.message)
  return
} 

if (paymentIntent.status === PaymentIntents.Status.RequiresConfirmation) {
  await createPaymentIntent({
    createPaymentIntentParams: {
      paymentIntentId: paymentIntent.id,
    },
    onSuccess: (data) => {
      const { error: requiresActionResponseError, status } = data

      if (requiresActionResponseError) {
        Alert.alert('Error', requiresActionResponseError)
        return
      } 
      if (status === 'active') {
        Alert.alert('Success', 'Pagamento efetuado com sucesso!')
      }
    },
    onError: () => Alert.alert('Error', 'Erro ao efetuar pagamento!'),
  })
}

}`

#

however, paymentIntent.status === PaymentIntents.Status.RequiresConfirmation is never true and call to the backend again never happens

mild shadow
#

Do you have a Payment Intent ID I can look at to see the lifecycle on my end?

mild shadow
#

Thanks, taking a look now

exotic ermine
mild shadow
#

I think what's happening here is you're using the wrong test card. That's my first guess. The test card being used is 4000002500003155 (see docs: https://stripe.com/docs/testing#three-ds-cards) which only requires authentication if you're not setting it up for future usage.

Can you run with the the card used for 'Always authenticate' (card number: 4000002760003184) and see if that results in the desired Payment Intent lifecycle?

Simulate payments to test your integration.

exotic ermine
ebon gulch
#

๐Ÿ‘‹ @exotic ermine stepping back in here.

#

Sorry I should have cleared this up earlier.... you can't use confirmation_method: manual with a Subscription

#

It is impossible

#

Thus you can't confirm the PaymentIntent on the server

#

When you use handleCardAction and complete 3DS, the PaymentIntent will be automatically moved to succeeded

#

You want to use the flow I mentioned earlier today since you have to use confirmation_method: automatic with Subscriptions

#

You also really don't want to be using manual confirmation here.... there are a lot of downsides.

#

The biggest one is that you will lose out on conversion

#

I mentioned this earlier as well, but if a customer drops off after completing 3DS but before the promise resolves then the PaymentIntent would just sit in requires_confirmation

#

Then their subscription will expire even though they think they have paid

#

You really just want to use the normal Subscription flow here and confirm client-side if 3DS is required and then use Webhooks for fulfillment

exotic ermine
#

ah, understood, I'm going to need to change my approach so anyway

#

this status 400 is normal?

ebon gulch
#

No

#

Are you forwarding events via the CLI?

exotic ermine
#

i'm using stripe listen --forward-to localhost:4242/webhook

#

app.post('/webhook', async (req, res) => { let data, eventType; // Check if webhook signing is configured. if (process.env.STRIPE_WEBHOOK_SECRET) { // Retrieve the event by verifying the signature using the raw body and secret. let event; let signature = req.headers['stripe-signature']; try { event = stripe.webhooks.constructEvent( req.rawBody, signature, process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { console.log(โš ๏ธ Webhook signature verification failed.); return res.sendStatus(400); } data = event.data; eventType = event.type; const dataObject = event.data.object; console.log(dataObject) } else { // Webhook signing is recommended, but if the secret is not configured in config.js`,
// we can retrieve the event data directly from the request body.
data = req.body.data;
eventType = req.body.type;
}

if (eventType === 'payment_intent.succeeded') {
// Funds have been captured
// Fulfill any orders, e-mail receipts, etc
// To cancel the payment after capture you will need to issue a Refund (https://stripe.com/docs/api/refunds)
console.log('๐Ÿ’ฐ Payment captured!');
} else if (eventType === 'payment_intent.payment_failed') {
console.log('โŒ Payment failed.');
}
res.sendStatus(200);
});`

ebon gulch
#

Can you send me one of those event IDs (evt_xxxx)?

exotic ermine
#

2022-05-12 18:43:10 --> payment_intent.created [evt_3KyjmqJ1bzV4VWW307WRz3ln] 2022-05-12 18:43:10 <-- [400] POST http://localhost:4242/webhook [evt_3KyjmqJ1bzV4VWW307WRz3ln]

ebon gulch
#

Ah okay thanks

#

This is your own server sending back a 400

#

So based on the code above looks like your webhook signature verification failed

#

What does your server log out?

#

When you are forwarding from the CLI you need to use the webhook secret that the CLI provides you

exotic ermine
exotic ermine
#

do i need to do anything else?

ebon gulch
#

Yeah your process.env.STRIPE_WEBHOOK_SECRET needs to be set to the CLI webhook secret

#

When you initiate stripe listen you will see a secret returned to you in your Terminal

exotic ermine
#

this secret Ready! You are using Stripe API Version [2020-08-27]. Your webhook signing secret is whsec_50e6998cd89cf3a877c762280fb9a805186949c5ee446a6ac2f58ed0358830ea (^C to quit) ?

ebon gulch
#

Yep

exotic ermine
#

`2022-05-12 18:57:21 --> payment_intent.succeeded [evt_3Kyk05J1bzV4VWW31nL8gIXL]
2022-05-12 18:57:21 --> charge.succeeded [evt_3Kyk05J1bzV4VWW31EbT8Cdr]
2022-05-12 18:57:21 <-- [200] POST http://localhost:4242/webhook [evt_3Kyk05J1bzV4VWW31nL8gIXL]
2022-05-12 18:57:21 <-- [200] POST http://localhost:4242/webhook [evt_3Kyk05J1bzV4VWW31EbT8Cdr]
2022-05-12 18:57:21 --> invoice.updated [evt_1Kyk0bJ1bzV4VWW3UWH7kRrO]
2022-05-12 18:57:21 <-- [200] POST http://localhost:4242/webhook [evt_1Kyk0bJ1bzV4VWW3UWH7kRrO]
2022-05-12 18:57:21 --> customer.subscription.updated [evt_1Kyk0bJ1bzV4VWW3HdrQ9ltq]
2022-05-12 18:57:22 <-- [200] POST http://localhost:4242/webhook [evt_1Kyk0bJ1bzV4VWW3HdrQ9ltq]
2022-05-12 18:57:22 --> invoice.paid [evt_1Kyk0bJ1bzV4VWW3YxL1tDkX]
2022-05-12 18:57:22 <-- [200] POST http://localhost:4242/webhook [evt_1Kyk0bJ1bzV4VWW3YxL1tDkX]
2022-05-12 18:57:22 --> invoice.payment_succeeded [evt_1Kyk0cJ1bzV4VWW3iHZv3RYo]
2022-05-12 18:57:22 <-- [200] POST http://localhost:4242/webhook [evt_1Kyk0cJ1bzV4VWW3iHZv3RYo]

`

ebon gulch
#

Yep that looks good

#

Now you are sending 200s back

exotic ermine
#

It worked!

ebon gulch
#

And should be able to handle the events

#

๐Ÿ™‚

exotic ermine
#

thanks a lot for the help, i'll see what i can do with the webhook

ebon gulch
#

Sounds good!

exotic ermine
#

can you help me in another topic that was open for me with the name alexzada - 3DS?

#

it was archived but i could not solve the problem

ebon gulch
#

Sure can you relay the issue here?

exotic ermine
#

Can I send the topic link here or do you want me to describe the problem again?

#

is that a dev was helping me, so there are already some things there

ebon gulch
#

Sure just paste the link and I'll look at the thread

ebon gulch
#

Oh

#

My colleague mentioned this earlier today

#

Yeah this is the issue with using Expo

#

That bug was fixed in version 0.2.4 of React Native SDK

#

But Expo limits you to 0.2.3

exotic ermine
#

something i can do to fix it?

#

it only happens when I use confirmPayment, which you recommended

ebon gulch
#

It should happen regardless of the method

#

It is a bug with the redirect back to your return URL

exotic ermine
#

with handleCardAction, it goes back to my app normally

ebon gulch
#

Let me double check but my understanding from the past is that the method shouldn't matter.

#

Ah you are correct

#

fix: Set returnUrl on Android in confirmPayment and confirmSetupIntent.

#

Interesting.

exotic ermine
#

what url do i need to put?

ebon gulch
#

I'm not intimately familiar with expo but are you using expo build or eas build?

exotic ermine
ebon gulch
#

Earlier you were able to build successfully with the newest version of React Native SDK, correct?

#

Even though you were using Expo?

ebon gulch
#

Well if you do that same thing here, and it doesn't break your build, and you use a version of the React Native SDK 0.4.0+ then this should get you around the Android redirect bug.

exotic ermine
#

so, i need "@stripe/stripe-react-native": "0.4.0"?

ebon gulch
#

That is the version that the Android redirect bug was fixed in, yes.

exotic ermine
#

and if it breaks my application what do i do? ๐Ÿ˜‚

ebon gulch
#

Which I believe removes the limitations of Expo Go and allows you to use newer versions of the SDK