+
+
+
+
diff --git a/woonuxt_base/app/composables/useAuth.ts b/woonuxt_base/app/composables/useAuth.ts
index 97ebf92d..e7db849c 100644
--- a/woonuxt_base/app/composables/useAuth.ts
+++ b/woonuxt_base/app/composables/useAuth.ts
@@ -12,6 +12,13 @@ export const useAuth = () => {
const orders = useState
('orders', () => null);
const downloads = useState('downloads', () => null);
+ onMounted(() => {
+ const savedCustomer = localStorage.getItem('WooNuxtCustomer');
+ if (savedCustomer) {
+ customer.value = JSON.parse(savedCustomer);
+ }
+ });
+
// Log in the user
const loginUser = async (credentials: CreateAccountInput): Promise<{ success: boolean; error: any }> => {
isPending.value = true;
diff --git a/woonuxt_base/app/composables/useCheckout.ts b/woonuxt_base/app/composables/useCheckout.ts
index ed873cd7..2b48e3a2 100644
--- a/woonuxt_base/app/composables/useCheckout.ts
+++ b/woonuxt_base/app/composables/useCheckout.ts
@@ -1,6 +1,12 @@
import type { CheckoutInput, UpdateCustomerInput, CreateAccountInput } from '#gql';
+import { StripePaymentMethodEnum } from '#gql/default';
+import type { CreateSourceData, Stripe, StripeCardElement, StripeElements } from '@stripe/stripe-js';
+import { CheckoutInlineError } from '../types/CheckoutInlineError';
export function useCheckout() {
+ const { t } = useI18n();
+ const { storeSettings } = useAppConfig();
+ const errorMessage = useState('errorMessage', () => null);
const orderInput = useState('orderInput', () => {
return {
customerNote: '',
@@ -10,6 +16,13 @@ export function useCheckout() {
};
});
+ onMounted(() => {
+ const savedOrderInput = localStorage.getItem('WooNuxtOrderInput');
+ if (savedOrderInput) {
+ orderInput.value = JSON.parse(savedOrderInput);
+ }
+ });
+
const isProcessingOrder = useState('isProcessingOrder', () => false);
// if Country or State are changed, calculate the shipping rates again
@@ -88,11 +101,11 @@ export function useCheckout() {
const orderId = checkout?.order?.databaseId;
const orderKey = checkout?.order?.orderKey;
- const orderInputPaymentId = orderInput.value.paymentMethod.id;
- const isPayPal = orderInputPaymentId === 'paypal' || orderInputPaymentId === 'ppcp-gateway';
+ const paymentMethodId = orderInput.value.paymentMethod.id;
+ const isPayPal = paymentMethodId === 'paypal' || paymentMethodId === 'ppcp-gateway';
// PayPal redirect
- if ((await checkout?.redirect) && isPayPal) {
+ if (checkout?.redirect && isPayPal) {
const frontEndUrl = window.location.origin;
let redirectUrl = checkout?.redirect ?? '';
@@ -112,34 +125,164 @@ export function useCheckout() {
router.push(`/checkout/order-received/${orderId}/?key=${orderKey}`);
}
- if ((await checkout?.result) !== 'success') {
- alert('There was an error processing your order. Please try again.');
+ if (checkout?.result !== 'success') {
+ alert(t('messages.error.orderFailed'));
window.location.reload();
- return checkout;
} else {
await emptyCart();
await refreshCart();
}
} catch (error: any) {
- isProcessingOrder.value = false;
-
const errorMessage = error?.gqlErrors?.[0].message;
if (errorMessage?.includes('An account is already registered with your email address')) {
alert('An account is already registered with your email address');
- return null;
+ } else {
+ alert(errorMessage);
}
+ } finally {
+ manageCheckoutLocalStorage(false);
+ isProcessingOrder.value = false;
+ }
+ };
+
+ const stripeCheckout = async (stripe: Stripe, elements: StripeElements) => {
+ let isPaid: boolean;
+
+ if (storeSettings.stripePaymentMethod === 'card') {
+ isPaid = await stripeCardCheckout(stripe, elements);
+
+ } else if (storeSettings.stripePaymentMethod === 'payment') {
+ isPaid = await stripePaymentCheckout(stripe, elements);
+ } else {
+ throw new Error("Invalid storeSettings.stripePaymentMethod");
+ }
+
+ if (isPaid) {
+ await proccessCheckout(true);
+ } else {
+ throw new Error(t('messages.error.orderFailed'));
+ }
+ }
+
+ const stripeCardCheckout = async (stripe: Stripe, elements: StripeElements) => {
+ const cardElement = elements.getElement('card') as StripeCardElement;
+ const { stripePaymentIntent } = await GqlGetStripePaymentIntent({ stripePaymentMethod: StripePaymentMethodEnum.SETUP });
+ const clientSecret = stripePaymentIntent?.clientSecret;
+ if (!clientSecret) throw new Error('Stripe PaymentIntent client secret missing!');
+
+ const { setupIntent, error } = await stripe.confirmCardSetup(clientSecret, { payment_method: { card: cardElement } });
+ if (error) {
+ throw new CheckoutInlineError(error.message);
+ }
+
+ const { source } = await stripe.createSource(cardElement as CreateSourceData);
+
+ if (source) orderInput.value.metaData.push({ key: '_stripe_source_id', value: source.id });
+ if (setupIntent) orderInput.value.metaData.push({ key: '_stripe_intent_id', value: setupIntent.id });
+
+ orderInput.value.transactionId = setupIntent?.id || stripePaymentIntent.id;
+
+ return setupIntent?.status === 'succeeded' || false;
+ };
+
+ const stripePaymentCheckout = async (stripe: Stripe, elements: StripeElements) => {
- alert(errorMessage);
- return null;
+ const { error: submitError } = await elements.submit();
+ if (submitError) {
+ throw new CheckoutInlineError(submitError.message);
}
- isProcessingOrder.value = false;
+ const { stripePaymentIntent } = await GqlGetStripePaymentIntent({ stripePaymentMethod: StripePaymentMethodEnum.PAYMENT });
+ const clientSecret = stripePaymentIntent?.clientSecret;
+ if (!clientSecret) throw new Error('Stripe PaymentIntent client secret missing!');
+ if (!stripePaymentIntent.id) throw new Error('Stripe PaymentIntent id missing!');
+
+ orderInput.value.metaData.push({ key: '_stripe_intent_id', value: stripePaymentIntent.id });
+ orderInput.value.transactionId = stripePaymentIntent.id;
+
+ // Let's save checkout orderInput & customer to maintain state after redirect
+ // We are not sure whether the confirmSetup will redirect if needed or continue code execution
+ manageCheckoutLocalStorage(true);
+
+ const { paymentIntent, error } = await stripe.confirmPayment({
+ elements,
+ clientSecret,
+ confirmParams: {
+ return_url: `${window.location.origin}/checkout`,
+ },
+ redirect: 'if_required',
+ });
+
+ if (error) {
+ throw new CheckoutInlineError(error.message);
+ }
+
+ return paymentIntent.status === 'succeeded' || false;
+ };
+
+ const validateStripePaymentFromRedirect = async (stripe: Stripe, clientSecret: string, redirectStatus: string) => {
+ try {
+ if (redirectStatus !== 'succeeded') throw new CheckoutInlineError(t('messages.error.paymentFailed'));
+
+ isProcessingOrder.value = true;
+ const { paymentIntent, error } = await stripe.retrievePaymentIntent(clientSecret);
+ if (error) {
+ throw new Error(error.message);
+ }
+
+ switch (paymentIntent?.status) {
+ case "succeeded":
+ await proccessCheckout(true);
+ break;
+ case "processing":
+ await proccessCheckout(false);
+ break;
+ case "requires_payment_method":
+ // If the payment attempt fails (for example due to a decline),
+ // the PaymentIntent’s status returns to requires_payment_method so that the payment can be retried.
+ throw new CheckoutInlineError(t('messages.error.paymentFailed'));
+ default:
+ throw new Error("Something went wrong. ('" + paymentIntent?.status + "')");
+ }
+ } catch (error: any) {
+ isProcessingOrder.value = false;
+ console.error(error);
+
+ useRouter().push({ query: {} });
+ manageCheckoutLocalStorage(false);
+
+ if (error instanceof CheckoutInlineError) {
+ errorMessage.value = error.message;
+ } else {
+ alert(error);
+ }
+ }
+ };
+
+ /**
+ * Manages the local storage for checkout data, specifically saving and removing
+ * the 'WooNuxtOrderInput' and 'WooNuxtCustomer' items. This is necessary to maintain
+ * the state after a redirect, ensuring the orderInput and customer information persist.
+ *
+ * @param {boolean} shouldStore - Indicates whether to save or remove the data in local storage.
+ */
+ const manageCheckoutLocalStorage = (shouldStore: boolean) => {
+ if (shouldStore) {
+ localStorage.setItem('WooNuxtOrderInput', JSON.stringify(orderInput.value));
+ localStorage.setItem('WooNuxtCustomer', JSON.stringify(useAuth().customer.value));
+ } else {
+ localStorage.removeItem('WooNuxtOrderInput');
+ localStorage.removeItem('WooNuxtCustomer');
+ }
};
return {
orderInput,
isProcessingOrder,
+ errorMessage,
+ stripeCheckout,
+ validateStripePaymentFromRedirect,
proccessCheckout,
updateShippingLocation,
};
diff --git a/woonuxt_base/app/locales/de-DE.json b/woonuxt_base/app/locales/de-DE.json
index 6ded0fff..10c1415c 100644
--- a/woonuxt_base/app/locales/de-DE.json
+++ b/woonuxt_base/app/locales/de-DE.json
@@ -159,6 +159,8 @@
"invalidPassword": "Ungültiges Passwort. Bitte versuchen Sie es erneut.",
"passwordMismatch": "Die Passwörter stimmen nicht überein. Bitte versuche es erneut.",
"invalidPasswordResetLink": "Der Passwort-Reset-Link ist ungültig.",
+ "orderFailed": "Bei der Bearbeitung Ihrer Bestellung ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.",
+ "paymentFailed": "Zahlung fehlgeschlagen. Bitte versuchen Sie es mit einer anderen Zahlungsmethode.",
"general": "Etwas ist schief gelaufen",
"noOrder": "Wir konnten Ihre Bestellung nicht finden. Bitte versuchen Sie es später noch einmal."
}
diff --git a/woonuxt_base/app/locales/en-US.json b/woonuxt_base/app/locales/en-US.json
index f0295d45..29de386d 100644
--- a/woonuxt_base/app/locales/en-US.json
+++ b/woonuxt_base/app/locales/en-US.json
@@ -159,6 +159,8 @@
"incorrectPassword": "Incorrect password. Please try again.",
"passwordMismatch": "Passwords do not match. Please try again.",
"invalidPasswordResetLink": "Password reset link is invalid.",
+ "orderFailed": "There was an error processing your order. Please try again.",
+ "paymentFailed": "Payment failed. Please try another payment method.",
"general": "Something went wrong",
"noOrder": "We could not find your order. Please try again later."
}
diff --git a/woonuxt_base/app/locales/es-ES.json b/woonuxt_base/app/locales/es-ES.json
index 534515a7..f72a31e9 100644
--- a/woonuxt_base/app/locales/es-ES.json
+++ b/woonuxt_base/app/locales/es-ES.json
@@ -159,6 +159,8 @@
"invalidPassword": "Contraseña inválida. Por favor, inténtalo de nuevo.",
"passwordMismatch": "Las contraseñas no coinciden. Por favor, inténtalo de nuevo.",
"invalidPasswordResetLink": "El enlace de restablecimiento de contraseña no es válido.",
+ "orderFailed": "Hubo un error al procesar su pedido. Por favor, inténtelo de nuevo.",
+ "paymentFailed": "El pago ha fallado. Por favor, intenta con otro método de pago.",
"general": "Ha ocurrido un error",
"noOrder": "No hemos podido encontrar tu pedido. Por favor, inténtalo de nuevo más tarde."
}
diff --git a/woonuxt_base/app/locales/fr-FR.json b/woonuxt_base/app/locales/fr-FR.json
index 2e102719..2a788f28 100644
--- a/woonuxt_base/app/locales/fr-FR.json
+++ b/woonuxt_base/app/locales/fr-FR.json
@@ -159,6 +159,8 @@
"incorrectPassword": "Mot de passe invalide. Veuillez réessayer.",
"passwordMismatch": "Les mots de passe ne correspondent pas. Veuillez réessayer.",
"invalidPasswordResetLink": "Le lien de réinitialisation du mot de passe est invalide.",
+ "orderFailed": "Une erreur s'est produite lors du traitement de votre commande. Veuillez réessayer.",
+ "paymentFailed": "Le paiement a échoué. Veuillez essayer un autre mode de paiement.",
"general": "Une erreur est survenue",
"noOrder": "Impossible de trouver votre commande. Veuillez réessayer plus tard."
}
diff --git a/woonuxt_base/app/locales/it-IT.json b/woonuxt_base/app/locales/it-IT.json
index d33ac07c..4744f8e7 100644
--- a/woonuxt_base/app/locales/it-IT.json
+++ b/woonuxt_base/app/locales/it-IT.json
@@ -159,6 +159,8 @@
"incorrectPassword": "Password errata. Riprova.",
"passwordMismatch": "Le password non coincidono. Per favore riprova.",
"invalidPasswordResetLink": "Il link per reimpostare la password non è valido.",
+ "orderFailed": "Si è verificato un errore durante l'elaborazione del tuo ordine. Per favore, riprova.",
+ "paymentFailed": "Pagamento fallito. Per favore, prova un altro metodo di pagamento.",
"general": "Qualcosa è andato storto",
"noOrder": "Non abbiamo trovato il tuo ordine. Riprova più tardi."
}
diff --git a/woonuxt_base/app/locales/pt-BR.json b/woonuxt_base/app/locales/pt-BR.json
index 10c36c7d..d896ee45 100644
--- a/woonuxt_base/app/locales/pt-BR.json
+++ b/woonuxt_base/app/locales/pt-BR.json
@@ -159,6 +159,8 @@
"incorrectPassword": "Senha incorreta. Por favor, tente novamente.",
"passwordMismatch": "As senhas não coincidem. Por favor, tente novamente.",
"invalidPasswordResetLink": "O link de redefinição de senha é inválido.",
+ "orderFailed": "Houve um erro ao processar seu pedido. Por favor, tente novamente.",
+ "paymentFailed": "Pagamento falhou. Por favor, tente outro método de pagamento.",
"general": "Algo deu errado",
"noOrder": "Não conseguimos encontrar seu pedido. Por favor, tente novamente mais tarde."
}
diff --git a/woonuxt_base/app/pages/checkout.vue b/woonuxt_base/app/pages/checkout.vue
index 73c42de7..bee69ce2 100644
--- a/woonuxt_base/app/pages/checkout.vue
+++ b/woonuxt_base/app/pages/checkout.vue
@@ -1,55 +1,80 @@