#edgar-3ds-redirect

1 messages ยท Page 1 of 1 (latest)

atomic cliffBOT
fringe sundial
#

Hello ๐Ÿ‘‹
I wasn't aware of full page redirects for 3DS
I thought they always used a modal

Can you provide more information about where you're seeing this? Is this with a specific test card?

lofty ore
#

yes, thats what was happening before, i believe it has to do with the fact that we now provide a returnUrl, thats the only change weve seen, but that was required only for cashapp payments

#

im using 4000 0000 0000 3220

#

and its redirecting me to this page

fringe sundial
fringe sundial
#

are you setting redirect: always in your code somewhere? nvm

#

is this page live? can you share the URL?

lofty ore
#

nop, its local , havent yet merged it to prod

fringe sundial
#

Can you share the code you're using for elements and confirmPayment call?

lofty ore
#

yes, give me a sec

#

so we are confirming on the server

#

the flow is, handleSubmit -> server-> handleServerResponse

#

the last one was handling all actions required for 3ds

fringe sundial
#

can you share it in codeblocks?
Tall images aren't readable on discord ๐Ÿ˜…

try this

lofty ore
#

oh sorry, yes

#
// Submit handler for all methods
  const handleSubmit = useCallback(
    async (evt: CheckoutEvent) => {
      if (!stripe) {
        return handleError(new Error('Stripe.js not loaded'));
      }
      setIsSubmitting(true);
      setErrorMessage('');
      if (!elements) {
        return handleError(new Error('Elements not loaded'));
      }

      const { error: submitError } = await elements.submit();

      if (submitError) {
        const paymentElement = elements.getElement('address');
        paymentElement?.focus();
        return handleError(submitError);
      }

      showElementLoadingState(true);

      const { error: paymentMethodError, paymentMethod } = await stripe.createPaymentMethod({
        elements,
      });

      if (paymentMethodError) {
        return handleError(paymentMethodError);
      }

      const addressElement = elements.getElement('address');
      let addressValue: DefaultValuesOption['billingDetails'] | object = {};
      if (addressElement) {
        const { value }: { value: DefaultValuesOption['billingDetails'] | object } =
          await addressElement.getValue();
        addressValue = transformProfileFromStripeData(value as StripeAddressElementData);
      }

      const { data } = await createStripePurchaseV2({
        variables: {
          purchaseId,
          paymentMethodId: paymentMethod.id,
          profile: addressValue,
          returnUrl,
        },
      });

      if (!data) {
        return null;
      }

      const trackingProps = extractTrackingProps({ evt, paymentMethod });

      return handleServerResponse({ subscription: data.createStripePurchaseV2, trackingProps });
    },
    [
      stripe,
      elements,
      showElementLoadingState,
      createStripePurchaseV2,
      purchaseId,
      returnUrl,
      handleServerResponse,
      handleError,
    ],
  );
#
 // 3d secure and/or success handler
  const handleServerResponse = useCallback(
    async ({
      subscription,
      trackingProps: incomingTrackingProps,
    }: {
      subscription: StripeSubscription;
      trackingProps: StripeCheckoutEventTrackingProps;
    }) => {
      const intent = (subscription.latestInvoice?.paymentIntent ||
        subscription.pendingSetupIntent) as unknown as StripePaymentIntent | StripeSetupIntent;

      if (!intent) {
        return handleError(new Error('Unexpected error: no intent found'));
      }

      const trackingProps = { ...incomingTrackingProps, intent };

      if (intent.status === SUCCEEDED) {
        return handleSuccess({ subscription, trackingProps });
      }

      if (intent.status === REQUIRES_ACTION) {
        if (!stripe) {
          return handleError(new Error('Stripe.js not loaded'));
        }

        const { error, paymentIntent, setupIntent }: Partial<PaymentIntentOrSetupIntentResult> =
          await stripe.handleNextAction({
            clientSecret: intent.clientSecret,
          });

        if (error) {
          return handleError(error);
        }

        if (paymentIntent?.last_payment_error) {
          return handleError(new Error(paymentIntent?.last_payment_error?.message));
        }

        if (setupIntent?.last_setup_error) {
          return handleError(new Error(setupIntent?.last_setup_error?.message));
        }

        if (paymentIntent?.status === SUCCEEDED || setupIntent?.status === SUCCEEDED) {
          return handleSuccess({ subscription, trackingProps });
        }
      }

      return handleError(new Error(`Unexpected error occurred for intent ${intent.id}`));
    },

    [handleError, handleSuccess, stripe],
  );
#

the server it just

with some tracking and local db code

fringe sundial
#

okay, so handleNextAction is what triggers the 3DS flow right?

lofty ore
#

yess

fringe sundial
#

we're looking, will respond once we find something.

In the meantime, if you're able to upload/setup a page where we can reproduce this then that'd be super helpful

lofty ore
#

awesome, thanks!

atomic cliffBOT
lofty ore
#

would a direct tunnel to my code work? using ngrok

cold hill
#

๐Ÿ‘‹ stepping in

#

Yes that would work

lofty ore
#

awesome, let me set it up

untold grail
#

edgar-3ds-redirect

#

Taking over after all @lofty ore I think the first step is to try and extremely simple/basic end to end example, if possible not using React because it makes it much harder for me to pair with you as I know nothing about React

#

Like right now you're doing something quite complex with React and Subscriptions and all that

#

It'd be best to "pause" and start with the most basic PaymentIntent creation

#

I'm trying in parallel too but want to make sure we talk about the same thing

#

Can you share the exact doc page you are following too if you have that?

lofty ore
untold grail
#

Yep just trying to get to the exact same state you are, there are between 7 and 2954 ways to integrate so it's so hard to grasp what people are seeing when doing obscure things in the first place

#

So would be great if we could pair and align on the most basic and simple behaviour first without Billing/Subscriptions

#

that doc you linked has 20 variations depending on the version and framework, any chance you can tell me which of the many sub-tabs you are on?

untold grail
#

Okay so let's ignore Subscription entirely for now, it's likely irrelevant

#

All I did was

  1. Create a PaymentIntent + confirm with pm_card_authenticationRequired + return_url
  2. Call handleNextAction() client-side which does a redirect.

And you're saying it didn't happen before and it would do the modal "in line"?

#

yeah okay I can reproduce, this feels like a real bug to me honestly. I don't think there's a way around it unfortunately if you use CashApp and you'll mostly have to deal with the redirect as is

lofty ore
#

yes, the modal was appearing everytime, i believe it has todo with the return_url param, we added it bc of cashapp

#

i see

untold grail
#

Ah my colleague found something I always forgot which is clearly in the docs you shared, did you miss it maybe?

#

ah it's because it's not in that tab, ugh

lofty ore
#

awesome, ill try that, what does that do?

untold grail
#

it explicitly says you plan to use one of our SDK (here Stripe.js).

lofty ore
#

i see, so should i leave that as true regardless of the payment method type?

#

(tested it, it works, thanks!)

untold grail
#

yeah I completely forgot this exists so all kudos to @cold hill. Makes kind of no sense to me since you are literally calling the Stripe.js function already and it's our SDK but ยฏ_(ใƒ„)_/ยฏ

lofty ore
#

haha no worries, i learned somehting today ๐Ÿ‘