connexion nouvelle api
This commit is contained in:
6
src/.env
6
src/.env
@@ -1,4 +1,4 @@
|
||||
VITE_SUPABASE_URL=https://supabase.abpcode.fr
|
||||
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzY2Nzc3OTU5LCJleHAiOjE5MjQ0NTc5NTl9.I-qytVb1ef6QMR8IUDePJzESO3bJAnsGE075XQ2xiaI
|
||||
VITE_STRIPE_PUBLISHABLE_KEY=pk_live_51RPSGmEPL3QASpovp8Q6p8ehNMW7TzSrOaV6zvPE1OtflMFN5jChQBEj5kr84wontlLOe8uiHyJBiCduzxIZwj5A00DIEVs31n
|
||||
VITE_STRIPE_PUBLISHABLE_KEY_DEV=pk_test_51RPSH1ERAUBjYKpgbz4GjZjDtI24rqfBky5SO6AwdBfZaqNmFN0zQSxx0Z1wfFKtKXIZXfx5IOQSt2ularULIsto00frDMNi03
|
||||
# VITE_STRIPE_PUBLISHABLE_KEY=pk_test_51RPSH1ERAUBjYKpgbz4GjZjDtI24rqfBky5SO6AwdBfZaqNmFN0zQSxx0Z1wfFKtKXIZXfx5IOQSt2ularULIsto00frDMNi03
|
||||
|
||||
VITE_API_URL=https://api.ditesleenchanson.fr
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@stripe/stripe-js": "^3.0.0",
|
||||
"@supabase/supabase-js": "^2.39.8",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"framer-motion": "^10.16.4",
|
||||
|
||||
@@ -38,14 +38,14 @@ import React from 'react';
|
||||
stripePriceId: 'price_1RVTXtEPL3QASpovaWjXf30q',
|
||||
description: 'Un texte de chanson unique, écrit sur mesure pour vous.',
|
||||
imageUrl: 'https://files.dites-le-en-chanson.fr/products/texte-personalise.jpg'
|
||||
// },
|
||||
// {
|
||||
// id: 'prod_SQMCFqFLaNHxx0',
|
||||
// name: 'Test',
|
||||
// price: 17.90,
|
||||
// promotionPrice: null,
|
||||
// stripePriceId: 'price_1RVVUBERAUBjYKpgYuQyuI76',
|
||||
// description: 'Un texte de chanson unique, écrit sur mesure pour vous.',
|
||||
},
|
||||
{
|
||||
id: 'prod_SQMCFqFLaNHxx0',
|
||||
name: 'Test',
|
||||
price: 17.90,
|
||||
promotionPrice: null,
|
||||
stripePriceId: 'price_1RVVUBERAUBjYKpgYuQyuI76',
|
||||
description: 'Un texte de chanson unique, écrit sur mesure pour vous.',
|
||||
// imageUrl: 'https://files.dites-le-en-chanson.fr/products/texte-personalise.jpg'
|
||||
}
|
||||
];
|
||||
|
||||
106
src/src/lib/apiClient.js
Normal file
106
src/src/lib/apiClient.js
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Client API pour remplacer Supabase
|
||||
* Communique avec l'API Laravel
|
||||
*/
|
||||
|
||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
|
||||
|
||||
/**
|
||||
* Effectue une requête HTTP vers l'API
|
||||
*/
|
||||
async function apiRequest(endpoint, options = {}) {
|
||||
const url = `${API_URL}${endpoint}`;
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers,
|
||||
};
|
||||
|
||||
const config = {
|
||||
...options,
|
||||
headers,
|
||||
};
|
||||
|
||||
if (options.body && typeof options.body === 'object') {
|
||||
config.body = JSON.stringify(options.body);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, config);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ error: 'Erreur inconnue' }));
|
||||
throw {
|
||||
message: errorData.error || `Erreur HTTP ${response.status}`,
|
||||
status: response.status,
|
||||
data: errorData,
|
||||
};
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
if (error.status) {
|
||||
throw error;
|
||||
}
|
||||
throw {
|
||||
message: error.message || 'Erreur de connexion',
|
||||
status: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API client pour remplacer Supabase
|
||||
*/
|
||||
export const api = {
|
||||
/**
|
||||
* Stripe - Création de session de checkout
|
||||
*/
|
||||
stripe: {
|
||||
/**
|
||||
* Crée une session de checkout Stripe
|
||||
*/
|
||||
async createCheckoutSession({ priceId, orderData, successUrl, cancelUrl, quantity, customerEmail }) {
|
||||
try {
|
||||
const data = await apiRequest('/stripe/create-checkout-session', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
priceId,
|
||||
orderData,
|
||||
successUrl,
|
||||
cancelUrl,
|
||||
quantity,
|
||||
customerEmail,
|
||||
},
|
||||
});
|
||||
return { data, error: null };
|
||||
} catch (error) {
|
||||
return { data: null, error };
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Commandes
|
||||
*/
|
||||
orders: {
|
||||
/**
|
||||
* Confirme une commande et envoie les emails
|
||||
*/
|
||||
async confirmOrder(orderData, sessionId) {
|
||||
try {
|
||||
const data = await apiRequest('/orders/confirm', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
orderData,
|
||||
sessionId,
|
||||
},
|
||||
});
|
||||
return { data, error: null };
|
||||
} catch (error) {
|
||||
return { data: null, error };
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
|
||||
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
|
||||
@@ -5,7 +5,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { CheckCircle, Package, Loader2, AlertTriangle, Info } from 'lucide-react';
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { api } from '@/lib/apiClient';
|
||||
|
||||
const ConfirmationPage = () => {
|
||||
const [orderDetails, setOrderDetails] = useState(null);
|
||||
@@ -77,30 +77,28 @@ import React, { useEffect, useState } from 'react';
|
||||
sessionStorage.setItem(processedKey, 'true');
|
||||
|
||||
try {
|
||||
const payload = { orderData: orderDataForDB, sessionId: sessionId };
|
||||
const { data: functionResponse, error: functionError } = await supabase.functions.invoke('send-order-confirmation-email', {
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const { data: functionResponse, error: functionError } = await api.orders.confirmOrder(orderDataForDB, sessionId);
|
||||
|
||||
if (functionError) {
|
||||
console.error("Supabase function invocation error:", functionError);
|
||||
throw new Error(functionError.message || "Erreur d'invocation de la fonction Edge.");
|
||||
console.error("API error:", functionError);
|
||||
throw new Error(functionError.message || "Erreur lors de l'appel à l'API.");
|
||||
}
|
||||
|
||||
if (!functionResponse) {
|
||||
console.error("No response data from edge function");
|
||||
throw new Error("Aucune donnée retournée par la fonction Edge.");
|
||||
console.error("No response data from API");
|
||||
throw new Error("Aucune donnée retournée par l'API.");
|
||||
}
|
||||
|
||||
if (functionResponse.orderId && functionResponse.orderId !== 'N/A') {
|
||||
const orderIdShort = functionResponse.orderId.toString().substring(0, 8);
|
||||
setOrderDetails(prev => ({ ...prev, id: functionResponse.orderId, customer_email: functionResponse.customerEmail || prev.customer_email }));
|
||||
toast({
|
||||
title: "Paiement Réussi!",
|
||||
description: `Votre commande (ID: ${functionResponse.orderId.substring(0,8)}) est enregistrée et un email de confirmation vous a été envoyé.`,
|
||||
description: `Votre commande (ID: ${orderIdShort}) est enregistrée et un email de confirmation vous a été envoyé.`,
|
||||
variant: "success",
|
||||
duration: 7000,
|
||||
});
|
||||
setMainStatus({ type: 'success', message: `Commande ${functionResponse.orderId.substring(0,8)} confirmée.` });
|
||||
setMainStatus({ type: 'success', message: `Commande ${orderIdShort} confirmée.` });
|
||||
} else {
|
||||
toast({
|
||||
title: "Problème Enregistrement Commande",
|
||||
|
||||
@@ -14,13 +14,9 @@ import {
|
||||
initialFormData,
|
||||
stepsConfig,
|
||||
formValidations,
|
||||
products,
|
||||
STRIPE_PUBLISHABLE_KEY
|
||||
products
|
||||
} from '@/config/orderFormConfig';
|
||||
import { loadStripe } from '@stripe/stripe-js';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
|
||||
const stripePromise = loadStripe(STRIPE_PUBLISHABLE_KEY);
|
||||
import { api } from '@/lib/apiClient';
|
||||
|
||||
const OrderPage = () => {
|
||||
const [ currentStep, setCurrentStep ] = useState(0);
|
||||
@@ -136,12 +132,6 @@ const OrderPage = () => {
|
||||
});
|
||||
|
||||
try {
|
||||
const stripe = await stripePromise;
|
||||
|
||||
if (!stripe) {
|
||||
throw new Error("Stripe n'a pas pu être initialisé. Vérifiez votre clé publique.");
|
||||
}
|
||||
|
||||
const selectedProductDetails = products.find(p => p.id === formData.selectedProduct);
|
||||
|
||||
if (!selectedProductDetails || !selectedProductDetails.stripePriceId || selectedProductDetails.stripePriceId.includes('YOUR_')) {
|
||||
@@ -184,17 +174,16 @@ const OrderPage = () => {
|
||||
localStorage.setItem('confirmedOrderDataForConfirmationPage', JSON.stringify(finalFormDataForConfirmation));
|
||||
localStorage.setItem('orderDataForDB', JSON.stringify(orderDataForDB));
|
||||
|
||||
// Créer la session Checkout via une fonction Supabase Edge
|
||||
// Créer la session Checkout via l'API Laravel
|
||||
console.log("Création de la session Checkout Stripe...");
|
||||
|
||||
const {data: sessionData, error: sessionError} = await supabase.functions.invoke('create-checkout-session', {
|
||||
body: JSON.stringify({
|
||||
priceId: selectedProductDetails.stripePriceId,
|
||||
quantity: 1,
|
||||
customerEmail: formData.email,
|
||||
successUrl: `${window.location.origin}/confirmation?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancelUrl: `${window.location.origin}/commander`,
|
||||
}),
|
||||
const {data: sessionData, error: sessionError} = await api.stripe.createCheckoutSession({
|
||||
priceId: selectedProductDetails.stripePriceId,
|
||||
orderData: orderDataForDB,
|
||||
quantity: 1,
|
||||
customerEmail: formData.email,
|
||||
successUrl: `${window.location.origin}/confirmation?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancelUrl: `${window.location.origin}/commander`,
|
||||
});
|
||||
|
||||
if (sessionError) {
|
||||
@@ -211,16 +200,16 @@ const OrderPage = () => {
|
||||
}
|
||||
|
||||
// Vérifier la structure de la réponse
|
||||
console.log("Réponse complète de la fonction Edge:", sessionData);
|
||||
console.log("Réponse complète de l'API:", sessionData);
|
||||
|
||||
// La réponse peut être directement sessionId ou dans un objet
|
||||
const sessionId = sessionData?.sessionId || sessionData?.id || sessionData;
|
||||
// La réponse contient sessionId et url
|
||||
const checkoutUrl = sessionData?.url;
|
||||
|
||||
if (!sessionId || typeof sessionId !== 'string') {
|
||||
console.error("Aucune session ID valide retournée:", sessionData);
|
||||
if (!checkoutUrl) {
|
||||
console.error("Aucune URL de checkout retournée:", sessionData);
|
||||
toast({
|
||||
title: "Erreur de paiement",
|
||||
description: "La session de paiement n'a pas pu être créée. Veuillez réessayer.",
|
||||
description: "L'URL de paiement n'a pas pu être générée. Veuillez réessayer.",
|
||||
variant: "destructive",
|
||||
});
|
||||
localStorage.removeItem('confirmedOrderDataForConfirmationPage');
|
||||
@@ -229,70 +218,9 @@ const OrderPage = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Rediriger vers Stripe avec le sessionId
|
||||
console.log("Redirection vers Stripe avec sessionId:", sessionId);
|
||||
|
||||
// Vérifier si on a une URL de checkout dans la réponse
|
||||
const checkoutUrl = sessionData?.url || sessionData?.checkoutUrl;
|
||||
|
||||
if (checkoutUrl) {
|
||||
// Si on a l'URL directement, l'utiliser
|
||||
console.log("Redirection vers l'URL de checkout fournie:", checkoutUrl);
|
||||
window.location.href = checkoutUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Sinon, utiliser redirectToCheckout de Stripe.js
|
||||
try {
|
||||
// Mémoriser l'URL actuelle pour détecter si la redirection a eu lieu
|
||||
const currentUrl = window.location.href;
|
||||
|
||||
const {error: redirectError} = await stripe.redirectToCheckout({
|
||||
sessionId: sessionId,
|
||||
});
|
||||
|
||||
if (redirectError) {
|
||||
console.error("Erreur Stripe lors de la redirection:", redirectError);
|
||||
toast({
|
||||
title: "Erreur de paiement",
|
||||
description: redirectError.message || "Une erreur est survenue lors de la redirection vers Stripe.",
|
||||
variant: "destructive",
|
||||
});
|
||||
localStorage.removeItem('confirmedOrderDataForConfirmationPage');
|
||||
localStorage.removeItem('orderDataForDB');
|
||||
setIsProcessingPayment(false);
|
||||
} else {
|
||||
// La redirection devrait se produire
|
||||
console.log("Redirection initiée avec succès via redirectToCheckout");
|
||||
|
||||
// Vérifier après un court délai si la redirection a réellement eu lieu
|
||||
// (fallback si redirectToCheckout ne fonctionne pas silencieusement)
|
||||
setTimeout(() => {
|
||||
if (window.location.href === currentUrl && document.visibilityState === 'visible') {
|
||||
console.warn("La redirection automatique n'a pas fonctionné, tentative alternative...");
|
||||
// Essayer une redirection manuelle via l'API Stripe
|
||||
// Note: Cette approche nécessite que la fonction Edge retourne l'URL de checkout
|
||||
// Pour l'instant, on affiche un message à l'utilisateur
|
||||
toast({
|
||||
title: "Redirection en cours...",
|
||||
description: "Si la redirection ne fonctionne pas, veuillez cliquer sur le lien de paiement.",
|
||||
variant: "default",
|
||||
});
|
||||
setIsProcessingPayment(false);
|
||||
}
|
||||
}, 1500);
|
||||
}
|
||||
} catch (redirectErr) {
|
||||
console.error("Exception lors de la redirection:", redirectErr);
|
||||
toast({
|
||||
title: "Erreur de paiement",
|
||||
description: redirectErr.message || "Une erreur est survenue lors de la redirection vers Stripe.",
|
||||
variant: "destructive",
|
||||
});
|
||||
localStorage.removeItem('confirmedOrderDataForConfirmationPage');
|
||||
localStorage.removeItem('orderDataForDB');
|
||||
setIsProcessingPayment(false);
|
||||
}
|
||||
// Rediriger vers Stripe avec l'URL de checkout
|
||||
console.log("Redirection vers l'URL de checkout:", checkoutUrl);
|
||||
window.location.href = checkoutUrl;
|
||||
} catch (err) {
|
||||
console.error("Erreur lors de la préparation du paiement:", err);
|
||||
toast({
|
||||
|
||||
Reference in New Issue
Block a user