#adnahalilovic_unexpected
1 messages ¡ Page 1 of 1 (latest)
đ Welcome to your new thread!
âąď¸ We automatically close idle threads, which makes them read-only. Make sure you stick around to chat in realtime!
đ This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1212769486726234184
đ Have more to share? You can add more detail below, including code, screenshots, videos, etc.
â˛ď¸ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question. Thank you for your patience!
Hello
Hi
So really, you should not be using custom inputs here! That is opening you up to a huge PCI burden.
That said, can you show me your code for how you are calling confirmSetupIntent?
` import { useConfirmSetupIntent } from '@stripe/stripe-react-native';
const { confirmSetupIntent, loading } = useConfirmSetupIntent();
const createSetupIntent = useMutation(
(requestBody: CreateSetupIntentDto) => {
return apiClient.stripe.createSetupIntent({ requestBody });
},
{
async onSuccess(res) {
const clientSecret = res.data.client_secret;
const { setupIntent, error } = await confirmSetupIntent(clientSecret, {
paymentMethodType: 'Card',
paymentMethodData: {
billingDetails: { email: (user as User)?.email },
},
});
if (error) {
if (error.message === 'Card details not complete')
navigation.goBack();
else
showToast({
type: 'error',
text: error?.message ?? 'Something went wrong.',
});
} else if (setupIntent) {
navigation.goBack();
}
},
onError(error: { body: BadRequestException }) {
showToast({
type: 'error',
text: error?.body?.message ?? 'Something went wrong.',
});
},
}
);
const createToken = useMutation(
(requestBody: CreateTokenDto) => {
return apiClient.stripe.createToken({ requestBody });
},
{
async onSuccess(res) {
createSetupIntent.mutate({ token: res.data.id });
},
onError(error: { body: BadRequestException }) {
showToast({
type: 'error',
text: error?.body?.message ?? 'Something went wrong.',
});
},
}
);`
on backend:
` async createSetupIntent(customerId: string, token: string) {
try {
const paymentMethod = await this.stripeClient.paymentMethods.create({
type: 'card',
card: {
token: token,
},
});
return await this.stripeClient.setupIntents
.create({
customer: customerId,
payment_method: paymentMethod.id,
confirm: true,
return_url: 'tonso-client://root/add-new-card',
})
.then(async (result) => {
return result;
})
.catch((error) => {
throw new BadRequestException(
(error as Error).message || 'Error while creating setup intent'
);
});
} catch (e) {
throw new BadRequestException(e.message || 'An error occurred');
}
}
async createToken(customerId: string, card: CardDto) {
try {
return await this.stripeClient.tokens
.create({
card,
})
.then(async (result) => {
return result;
})
.catch((error) => {
throw new BadRequestException(
(error as Error).message || 'Error while creating token'
);
});
} catch (e) {
throw new BadRequestException(e.message || 'An error occurred');
}
}`
Alright first, you can cut out one of your backend requests by just passing the card data directly to paymentMethods.create()
You don't need to create a Token in a separate request.
That said, the reason you are seeing the error is that you aren't checking what happens with the SetupIntent when you confirm in your backend.
If 3DS isn't required, it will succeed in your backend and you don't need to confirm again in your frontend.
So you need to look at the status of the SetupIntent in your backend after that request, and only confirm in your frontend if the status is requires_action
so you say error wont appear if 3ds isnt required, but this error happens when I try to enter card which requires 3ds , too
Can you run a fresh test with a 3DS required card and provide me the SetupIntent ID?
{"application": null, "automatic_payment_methods": {"allow_redirects": "always", "enabled": true}, "cancellation_reason": null, "client_secret": "seti_1OpAt1LiTCnJ4Ibr3kawwZeZ_secret_PeTqoMvs7HprDdQhWr5rwnunrTOC9Xq", "created": 1709218023, "customer": "cus_PdHVcPzZQM9Eze", "description": null, "flow_directions": null, "id": "seti_1OpAt1LiTCnJ4Ibr3kawwZeZ", "last_setup_error": null, "latest_attempt": "setatt_1OpAt1LiTCnJ4IbrhWtfwCnX", "livemode": false, "mandate": null, "metadata": {}, "next_action": {"redirect_to_url": {"return_url": "tonso-client://root/add-new-card", "url": "https://hooks.stripe.com/3d_secure_2/hosted?merchant=acct_1NE1ECLiTCnJ4Ibr&publishable_key=pk_test_51NE1ECLiTCnJ4IbrfDsrlpwBZY5zFHuAkeP9paXvfuuzW1N0znimoOrqIetCG7M37ViWX5KUAOGtOXfdyUNfCzks008tsxGY0g&setup_intent=seti_1OpAt1LiTCnJ4Ibr3kawwZeZ&setup_intent_client_secret=seti_1OpAt1LiTCnJ4Ibr3kawwZeZ_secret_PeTqoMvs7HprDdQhWr5rwnunrTOC9Xq&source=src_1OpAt1LiTCnJ4Ibrx7lH6mhK"}, "type": "redirect_to_url"}, "object": "setup_intent", "on_behalf_of": null, "payment_method": "pm_1OpAt1LiTCnJ4IbrGxF8PZWM", "payment_method_configuration_details": {"id": "pmc_1OKWunLiTCnJ4IbrQHYVFkoM", "parent": null}, "payment_method_options": {"card": {"mandate_options": null, "network": null, "request_three_d_secure": "automatic"}}, "payment_method_types": ["card", "bancontact", "ideal", "link"], "single_use_mandate": null, "status": "requires_action", "usage": "off_session"}
this is response of create setup intent
for 3ds required card
Okay sure, but I don't see any frontend confirmation at all
Did you add a log to your frontend to ensure it is the same SetupIntent you are confirming on the frontend?
I thought that confirmSetupIntent would automtically redirect, because Im doing similar thing for payment
const createIntent = useMutation( (requestBody: CreatePaymentIntentDto) => { return apiClient.stripe.createPaymentIntent({ requestBody }); }, { async onSuccess(res) { const clientSecret = res.data.client_secret; const { error, paymentIntent } = await confirmPayment(clientSecret, { paymentMethodType: 'Card', paymentMethodData: { paymentMethodId: selectedCard?.id, }, }); if (error) { showToast({ type: 'error', text: 'Payment failed. Please try again.', }); setLoading(false); setPaymentAuthFailed(true); } else if (paymentIntent.status === 'Succeeded') { updateBooking.mutate({ paid: true, paymentIntentId: paymentIntent.id, percentageFee: totalPrice ? totalPrice * (feeConfig?.tonsoFeePercentage / 100) : 0, flatFee: feeConfig?.tonsoFeePercentage, }); } }, onError(error: { body: BadRequestException }) { setLoading(false); showToast({ type: 'error', text: error?.body?.message ?? 'Something went wrong.', }); }, } );
import { confirmPayment } from '@stripe/stripe-react-native';
this confirmPayment automatically redirects to 3ds screen if needed
Yes, if 3DS is required then confirmSetupIntent() would indeed handle showing the 3DS modal.
But what I'm telling you from your example right now is that confirmSetupIntent() never ran on your frontend for that SetupIntent
You can look in your Dashboard here: https://dashboard.stripe.com/test/setup_intents/seti_1OpAt1LiTCnJ4Ibr3kawwZeZ
And you can see that the only thing that ever occurred was the backend creation request
There was no frontend confirmation request from calling confirmSetupIntent() on your frontend
Im trying to call confirmSetupIntent
const { setupIntent, error } = await confirmSetupIntent(clientSecret, { paymentMethodType: 'Card', paymentMethodData: { billingDetails: { email: (user as User)?.email }, }, }); if (error) { if (error.message === 'Card details not complete') navigation.goBack(); else showToast({ type: 'error', text: error?.message ?? 'Something went wrong.', }); } else if (setupIntent) { navigation.goBack(); } },
but I get error {"code": "Failed", "declineCode": null, "localizedMessage": "Card details not complete", "message": "Card details not complete", "stripeErrorCode": null, "type": null}
Can you add a log for the clientSecret there?
Right before you call confirmSetupIntent()
seti_1OpAt1LiTCnJ4Ibr3kawwZeZ_secret_PeTqoMvs7HprDdQhWr5rwnunrTOC9Xq
Alright well my understanding is that indeed should just work. However, maybe this is a bit different in the React Native SDK than the others for a SetupIntent. Can you try using handleNextActionForSetup() (https://stripe.dev/stripe-react-native/api-reference/index.html#handleNextActionForSetup) instead there?
You too!
const { setupIntent, error } = await handleNextActionForSetup( clientSecret, 'tonso-client://root/add-new-card' );
after successful auth, it doesnt redirect me back to app, do you have idea why
i have to go back to app by myself
@austere mulch
Sounds like your deep link isn't set up correctly in that case?
when I try to enter link manually in browser, it redirects me to right screen
What happens if you just put an HTTPS URL in there?
Do you get redirected to that URL?
I didnt make universal link for this case yet
also, this works for confirm payment intent
it redirects me automatically
const { error, paymentIntent } = await confirmPayment(clientSecret, { paymentMethodType: 'Card', paymentMethodData: { paymentMethodId: selectedCard?.id, }, });
`await this.stripeClient.paymentIntents
.create({
...data,
confirmation_method: 'automatic',
confirm: true,
return_url: 'tonso-client://root/payment',
use_stripe_sdk: true,
})
await this.stripeClient.setupIntents
.create({
customer: customerId,
payment_method: paymentMethod.id,
confirm: true,
return_url: 'tonso-client://root/add-new-card',
})`
i defined return url in both stripe api calls
let me try adding use_stripe_sdk: true,