#neatz_code

1 messages ยท Page 1 of 1 (latest)

main crestBOT
#

๐Ÿ‘‹ Welcome to your new thread!

โฒ๏ธ 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.

โฑ๏ธ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.

๐Ÿ”— 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/1372578038252044310

๐Ÿ“ Have more to share? Add more details, code, screenshots, videos, etc. below.

grand lodge
#
const PaymentForm = ({
  onSuccess,
}: Omit<Props, "stripePromise" | "clientSecret">) => {
  const stripe = useStripe();
  const elements = useElements();
  const [isLoading, setIsLoading] = useState(false);
  const [isReady, setIsReady] = useState(false);
  const { session } = useSession();
  const { setPaymentMethod, plan } = useSubscription();
  const planData = SubscriptionPlans[plan as keyof typeof SubscriptionPlans];
  const isMobile = useMediaQuery("(max-width: 768px)");

  const handleSubmit = async (e?: React.FormEvent) => {
    e?.preventDefault();

    if (!stripe || !elements) {
      return;
    }

    setIsLoading(true);

    try {
      const { error } = await elements.submit();
      if (error) {
        console.error(error);
      }

      const { error: submitError, paymentIntent } = await stripe.confirmPayment(
        {
          elements,
          redirect: "if_required",
          confirmParams: {
            return_url: window.location.href,
          },
        }
      );

      if (submitError) {
        console.error(submitError);
        if (["card_error"].includes(submitError.type)) {
          throw new Error(submitError.message);
        }
      }

      if (paymentIntent?.status === "succeeded") {
        onSuccess();
      }
    } catch (error) {
      console.error(error);
      const errorMessage =
        error instanceof Error
          ? error.message
          : (error as StripeError)?.message || "An unexpected error occurred";
      showNotification({
        color: "red",
        autoClose: 5000,
        message: errorMessage,
        title: "Error",
      });
    } finally {
      setIsLoading(false);
    }
  };

  let paymentMethodOrder = ["apple_pay", "google_pay", "card", "cashapp_pay"];
  if (!isMobile) {
    paymentMethodOrder = ["card", "cashapp_pay", "google_pay", "apple_pay"];
  }

#
  return (
    <form onSubmit={handleSubmit} style={{ width: "100%" }}>
      <Stack p="sm" gap="sm">
        <PaymentElement
          onReady={() => setIsReady(true)}
          onChange={(e) => {
            setPaymentMethod(e.value.type as any);
            // Don't auto submit cards
            if (isReady && e.complete && e.value.type !== "card") {
              handleSubmit();
            }
          }}
          options={{
            layout: {
              type: "tabs",
              defaultCollapsed: false,
            },
            paymentMethodOrder,
            applePay: {
              recurringPaymentRequest: {
                paymentDescription: "BP Pro Subscription",
                managementURL: `https://breakingpoint.gg/profile/${session?.userProfile?.id}`,
                regularBilling: {
                  amount: planData.price * 100,
                  label: "Monthly subscription fee",
                  recurringPaymentIntervalUnit:
                    plan === "monthly" ? "month" : "year",
                  recurringPaymentIntervalCount: 1,
                },
              },
            },
          }}
        />
        <Button
          fullWidth
          size="md"
          variant="light"
          color="#e6e089"
          type="submit"
          loading={isLoading}
          disabled={!stripe || !elements}
        >
          Subscribe for ${planData.price}
        </Button>
      </Stack>
    </form>
  );
};
#

So the idea here is to auto-initiate the apple pay (or whatever pay) process immediately when the form works to prevent requiring another button click. Everything works perfect for google pay, cashapp pay, etc, but apple pay throws an error

{
    type: 'invalid_request_error',
    message: 'Something went wrong. Unable to show Apple Pay. Please choose a different payment method and try again.',
    extra_fields: { localized: true }
  }
#

To note: apple pay works perfectly fine when you manually click the Subscribe button which submits the form, so not quite sure whats causing the different, just curious if this is possible

main crestBOT
dreamy glade
#

Hello there

#

Is there a request ID corresponding with that error? Or where do you see that exactly?

#

As far as I know Apple has security limitations that require a real button click to open its payment modal.

grand lodge
#

Its from both await elements.submit and await stripe.confirmPayment

#

Requiring a real button click makes sense, however we still require users to click buttons to initialize the payment flow, its just at the step of showing the PaymentElement that we don't want a manual button click

#

For context, there are 3 slides in a modal windows, first shows the plan options, second shows the PaymentElement, third shows a success screen

#

Ideally users select a plan, then when the second slide shows up its automatically processing with apple pay if available

#

I tried submitting by attaching a ref to the Button and submitting through a click, but no luck there

dreamy glade
#

Is there somewhere I can reproduce what you are seeing to inspect the error myself? I do believe this is a limitation on Apple Pay's side and don't think there is any way around it, but would help me double check.

grand lodge
#

Like a link to our site?

dreamy glade
#

Yeah

grand lodge
#

this "Go Ad Free" button initiates that payment flow

dreamy glade
#

Thanks, let me take a quick look

#

Hmm wait when do you see the error exactly?

#

Because it just worked fine for me

grand lodge
#

It auto submitted apple pay?

dreamy glade
#

(The modal displays without error when I switch selection from Card to Apple Pay)

#

Do you see it when actually attempting to pay?

grand lodge
#

ah it would need to be the default payment method

#

it works fine if you manually click Apple Pay

dreamy glade
#

Oh you mean if it is first in the payment method order

grand lodge
#

yes

#

Are you on mac

dreamy glade
#

Yes

grand lodge
#

ok let me change the order

dreamy glade
#

๐Ÿ‘

grand lodge
#

ok try now

dreamy glade
#

Yep so the real error is shown in Console here:

[Error] IntegrationError: The code that shows the Apple Pay payment sheet must be invoked directly by a user activation event, like a click or a touch gesture. To prevent this error make sure the code that shows the payment sheet is at or near the top of your user gesture event handler, before any async or long-running code.
#

That is the Apple error that they throw

#

I wonder if you added a short delay if it would work? Since I'm actually quite surprised it works when switching from card to Apple Pay itself.

#

And to be clear you are calling elements.submit() immediately in your click handler?

#

You don't fetch your backend or anything like that.

grand lodge
#

yea the code I posted is exactly what its doing right now

#

ive tried a delay but let me try again

dreamy glade
#

Yeah now that I think about it the user click to change payment method types from card to Apple Pay likely solves the Apple security limitation.

#

So that actually does make sense

#

They don't really care what is used as the trigger as long as it is an actual user click/gesture.

#

Don't think you are going to be able to get around this...

grand lodge
#

hmmmmm that does make sense

#

Maybe i can re-use the click event for the initial slide

#

cuz yea, using a ref on the Button and clicking it through code doesn't work either

#

im interested in what distinguishes between a real and fake click like that

dreamy glade
#

Yeah their security check wouldn't be very good if it was easy to circumvent ๐Ÿ™‚

grand lodge
#

The current code isn't even passing that click event to handleSubmit, it must be handled internally?

#

Since it works when swapping payment methods

dreamy glade
#

I think the check just involves any sort of click within ~1 second of the modal being prompted.

#

That's my understanding

grand lodge
#

hmm yea i might need to restructure the payment process it seems

dreamy glade
#

Yeah or just don't have Apple Pay as the first option?

grand lodge
#

yes but the idea was to auto start the apple pay process if its available

#

instead of requiring another button click

dreamy glade
#

Yeah I understand, but yeah that's not possible so going to have to go with something else. Just saying I'm not sure you have to restructure everything if you don't wan to, but do understand you won't be able to accomplish that specific flow.

grand lodge
#

Or does it just mean any click?

dreamy glade
#

Not sure tbh

#

I haven't really tried getting around this check

#

I don't know the intricacies of how it works but folks usually run into it when trying to call their backend in their click handler which then times out the check

grand lodge
#

yea just because there is a button which opens the PaymentElement, just thinking about why that click doesnt count

#

maybe the PaymentElement needs to be mounted first

#

and it listens for clicks

dreamy glade
#

Yeah the associated Apple Pay code has to be mounted afaik. Which is derived from Payment Element rendering Apple Pay. This is not our code specifically though -- it is our Apple Pay implementation.

grand lodge
#

worth a shot at least ๐Ÿ˜„ thanks for the help