#alexzada-3ds-subscriptions
1 messages ยท Page 1 of 1 (latest)
Here: https://stripe.com/docs/js/payment_intents/handle_card_action
And here: https://stripe.com/docs/payments/accept-a-payment-synchronously#web-handle-next-actions
Do those help?
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
Do you have a Payment Intent ID I can look at to see the lifecycle on my end?
req_WrwewGPkMSSnAj
Thanks, taking a look now
ok
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?
const { error: requiresActionError, paymentIntent } = await handleCardAction(paymentIntentClientSecret) console.log('paymentIntent', paymentIntent.status) results paymentIntent Succeeded
๐ @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
ah, understood, I'm going to need to change my approach so anyway
this status 400 is normal?
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);
});`
Can you send me one of those event IDs (evt_xxxx)?
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]
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
โ ๏ธ Webhook signature verification failed.
the only thing i do is stripe listen --forward-to localhost:4242/webhook
do i need to do anything else?
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
this secret Ready! You are using Stripe API Version [2020-08-27]. Your webhook signing secret is whsec_50e6998cd89cf3a877c762280fb9a805186949c5ee446a6ac2f58ed0358830ea (^C to quit) ?
Yep
`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]
`
It worked!
thanks a lot for the help, i'll see what i can do with the webhook
Sounds good!
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
Sure can you relay the issue here?
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
Sure just paste the link and I'll look at the thread
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
something i can do to fix it?
it only happens when I use confirmPayment, which you recommended
It should happen regardless of the method
It is a bug with the redirect back to your return URL
with handleCardAction, it goes back to my app normally
Let me double check but my understanding from the past is that the method shouldn't matter.
Ah you are correct
Here are details about the bug: https://github.com/stripe/stripe-react-native/blob/f33a7268b1b801e51875d32a8227d0534d28c1fa/CHANGELOG.md#040
fix: Set returnUrl on Android in confirmPayment and confirmSetupIntent.
Interesting.
what url do i need to put?
I'm not intimately familiar with expo but are you using expo build or eas build?
I confess I don't know, but I don't remember seeing eas anywhere
Earlier you were able to build successfully with the newest version of React Native SDK, correct?
Even though you were using Expo?
yep
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.
so, i need "@stripe/stripe-react-native": "0.4.0"?
That is the version that the Android redirect bug was fixed in, yes.
and if it breaks my application what do i do? ๐
I believe the other option is to use an Expo SDK 45 beta: https://blog.expo.dev/expo-sdk-45-beta-is-now-available-88507051f6d2?gi=ad50cff284de
That beta SDK bumps to Stripe 0.6.0: https://github.com/expo/expo/blob/sdk-45/CHANGELOG.md#4500--2022-04-25
You may also want to look into the specifics around eas build (https://docs.expo.dev/development/introduction/#from-expo-go-to-development-builds)
Which I believe removes the limitations of Expo Go and allows you to use newer versions of the SDK