vendure-ecommerce / storefront-qwik-starter

An e-commerce storefront starter built with Qwik and Vendure
https://qwik-storefront.vendure.io
227 stars 86 forks source link

Checkout with Stripe does not redirect to a confirmation page #145

Closed bulecampur closed 9 months ago

bulecampur commented 10 months ago

Hi, I am building a store with your wonderful template - thank you so much for this. I have made some customization but they should not touch the issue I have been having since the issue is related to the Stripe Plugin, which I have not touched.

The issue is that when an order is paid via Stripe (Test mode), the payment is made and registered in Stripe but the client is not redirected to the confirmation page. It also seems like the confirmation page was not created. StripeConfirmationError

This is not true for the dummy payment handler, which creates the payment. dummyPaymentHandler

What am I doing wrong here or is this an issue in the code? It seems like the issue is with onForward$, which is only triggered by the payment component, not by the Stripe Payment component. Instead Stripe points towards a page that does not exist yet because it has not transitioned the state. Or is it a different issue?

How can I get Stripe Payment to trigget onForward$() ?

gioboa commented 10 months ago

Hi, with Stripe do you have the confirmed order in your vendure environment? @prasmalla do you have any feedback?

bulecampur commented 10 months ago

Ok, it seems like I only see it in Stripe. In Vendure I see it as "Adding Items" not as Payment processed. Edit: I can see the payments in Vendure now as Payment Settled. But the original issue is still there.

bulecampur commented 10 months ago

This is the Stripe Component


import {
    $,
    component$,
    noSerialize,
    useContext,
    useStore,
    useVisibleTask$,
} from '@builder.io/qwik';
import { useLocation } from '@builder.io/qwik-city';
import type { Stripe, StripeElements } from '@stripe/stripe-js';
import { loadStripe } from '@stripe/stripe-js';
import { APP_STATE } from '~/constants';
import { ENV_VARIABLES } from '~/env';
import { createStripePaymentIntentMutation } from '~/providers/shop/checkout/checkout';
import CreditCardIcon from '../icons/CreditCardIcon';
import XCircleIcon from '../icons/XCircleIcon';

let _stripe: Promise<Stripe | null>;
function getStripe(publishableKey: string) {
    if (!_stripe && publishableKey) {
        _stripe = loadStripe(publishableKey);
    }
    return _stripe;
}
const stripePromise = getStripe(ENV_VARIABLES.VITE_STRIPE_PUBLISHABLE_KEY);

export default component$(() => {
    const appState = useContext(APP_STATE);
    const baseUrl = useLocation().url.origin;
    const store = useStore({
        clientSecret: '',
        resolvedStripe: noSerialize({} as Stripe),
        stripeElements: noSerialize({} as StripeElements),
        error: '',
    });
    useVisibleTask$(async () => {
        store.clientSecret = await createStripePaymentIntentMutation();

        await stripePromise.then((stripe) => {
            store.resolvedStripe = noSerialize(stripe as Stripe);
            store.stripeElements = noSerialize(stripe?.elements({ clientSecret: store.clientSecret }));
            store.stripeElements?.create('payment').mount('#payment-form');
        });
    });

    return (
        <div class="flex flex-col items-center max-w-xs">
            <div id="payment-form" class="mb-8"></div>
            {store.error !== '' && (
                <div class="rounded-md bg-red-50 p-4 mb-8">
                    <div class="flex">
                        <div class="flex-shrink-0">
                            <XCircleIcon />
                        </div>
                        <div class="ml-3">
                            <h3 class="text-sm font-medium text-red-800">We ran into a problem with payment!</h3>
                            <p class="text-sm text-red-700 mt-2">{store.error}</p>
                        </div>
                    </div>
                </div>
            )}

            <button
                class="flex px-6 bg-primary-600 hover:bg-primary-700 items-center justify-center space-x-2 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
                disabled={!_stripe}
                onClick$={$(async () => {
                    const result = await store.stripeElements?.submit();
                    if (!result?.error) {
                        const result = await store.resolvedStripe?.confirmPayment({
                            elements: store.stripeElements,
                            clientSecret: store.clientSecret,
                            confirmParams: {
                                return_url: `${baseUrl}/checkout/confirmation/${appState.activeOrder.code}`,
                            },
                        });

                        if (result?.error) {
                            store.error = result.error.message as string;
                        }
                    } else {
                        store.error = result.error.message as string;
                    }
                })}
            >
                <CreditCardIcon />
                <span>Pay with Stripe</span>
            </button>
        </div>
    );
});`
bulecampur commented 10 months ago

Ok, it seems like I only see it in Stripe. In Vendure I see it as "Adding Items" not as Payment processed.

Ignore this, I can see it in Vendure now - I just had to update the Stripe Webhook for the Stripe Test Environment. So it does show as Payment Settled as expected. But the issue with the Order Confirmation Page is still there

prasmalla commented 10 months ago

have not seen this issue before, do you have a repo for us to reproduce this behavior?

bulecampur commented 10 months ago

I have created a repo that demonstrates the error: one would need a Stripe test api though and the linked Vendure backend. Here is the repo https://github.com/bulecampur/stripe-confirmation-page Excuse the atrocious buttons, I have not configured the checkout pages yet so they are difficult to see. right now (basically white on white). I am guessing since the Stripe Component redirects towards a page but the state has not changed to "Confirmation", the stripe link cannot find anything. This may also be why items are left in the cart. If we use the dummy payment provider, it generates the page through onForward$. Is this a correct assessment?

bulecampur commented 9 months ago

So I looked further at it and the behavior now is the following:

This could lead to confusion by the customers. Í tried to add a UseVisibileTask$ to automatically reload the page with useNavigate. But this does not work. Are there any suggestions? What could be the issue? Do you not see this issue in your implementation?

gioboa commented 9 months ago

I think the error is here _.Stripe returns the returnurl, but it does not load the confirmation page, yet. why is not working?

bulecampur commented 9 months ago

Note that the error is on a deploy to Cloudflare pages. Is it a routing error or is it a timing error? Maybe my Vendure has not transitioned to the state paymentSettled the moment the confirmation page is loaded? And only transitions after as the stripe plugin gets confirmation from stripe. Does it work on your clean deploy of the qwik Vendure storefront? I'm just throwing around guesses...

gioboa commented 9 months ago

maybe you can add a timeout here for testing purpose. You redirecting here, isn'it?

bulecampur commented 9 months ago

Yes, thanks for the suggestion, I will try it.

⁣Get BlueMail for Android ​

On Jan 23, 2024, 6:08 PM, at 6:08 PM, Giorgio Boa @.***> wrote:

maybe you can add a timeout here for testing purpose. You redirecting here, isn'it?

-- Reply to this email directly or view it on GitHub: https://github.com/vendure-ecommerce/storefront-qwik-starter/issues/145#issuecomment-1906531690 You are receiving this because you authored the thread.

Message ID: @.***>

bulecampur commented 9 months ago

maybe you can add a timeout here for testing purpose. You redirecting here, isn'it?

So when I come through stripe, it resolves (timeout 5000 ms), however the store.order is null. When I reload manually, it actually loads the order.

gioboa commented 9 months ago

🤔 Is It grabbing the correct order code from the URL?

bulecampur commented 9 months ago

Yes, the order code is correct as per console.log

gioboa commented 9 months ago

You can execute the gql call in the browser by changing this code. We can look at the network tab to see the response.

bulecampur commented 9 months ago

You can execute the gql call in the browser by changing this code. We can look at the network tab to see the response.

What should I change exactly?

So I tried to log what is happening. When I land via stripe and I console.log the activeOrder from the context, it still shows up. This should not happen right? Whereas the order retrieved by the query returns null. When I manually reload, the activeOrder does not show anymore meaning that it has transitioned and the order by code query now returns the correct order.

gioboa commented 9 months ago

I see. The order retrieved by the query should always return the confirmed order.

bulecampur commented 9 months ago

Ok, so it seems like there is a 5-10 seconds delay until the confirmed order is ready. Using window.reload with a timer eventually gets there. But can that be the solution though?

gioboa commented 9 months ago

Did you check the vendure dashboard? Is it slow there too?

bulecampur commented 9 months ago

The backend seems to work fine, it's probably just waiting for the stripe confirmation via webhook? that causes this time dissonance. For now I have patched this with:

useVisibleTask$(async () => { const delayDuration = 5000; // Set the delay duration in milliseconds setTimeout(async () => { // Check if store.order is null before calling the navigate function if (store.order === null) { // Reload the page if order is still null location.reload(); } }, delayDuration); });

prasmalla commented 9 months ago

also using this in production on cloudflare with timeout to refresh the page but it's for the business logic. does it work without the refresh in your local env?

bulecampur commented 9 months ago

I think you need to jump through additional hoops to test stripe locally, isn't that the case? So I did not test it locally yet.