diff --git a/src/App.js b/src/App.js index 7de70ab..825152a 100644 --- a/src/App.js +++ b/src/App.js @@ -55,6 +55,9 @@ const ChatAssistant = lazy(() => import(/* webpackChunkName: "chat" */ "./compon const PresseverleihPage = lazy(() => import(/* webpackChunkName: "presseverleih" */ "./pages/PresseverleihPage.js")); const ThcTestPage = lazy(() => import(/* webpackChunkName: "thc-test" */ "./pages/ThcTestPage.js")); +// Lazy load payment success page +const PaymentSuccess = lazy(() => import(/* webpackChunkName: "payment" */ "./components/PaymentSuccess.js")); + // Import theme from separate file to reduce main bundle size import defaultTheme from "./theme.js"; // Lazy load theme customizer for development only @@ -224,6 +227,9 @@ const AppContent = ({ currentTheme, onThemeChange }) => { {/* Profile page */} } /> + {/* Payment success page for Mollie redirects */} + } /> + {/* Reset password page */} { + try { + // Get the stored payment ID from localStorage + const pendingPayment = localStorage.getItem('pendingPayment'); + + if (!pendingPayment) { + console.error('No pending payment found in localStorage'); + this.setState({ + redirectUrl: '/profile#cart', + processing: false, + error: 'No payment information found' + }); + return; + } + + let paymentData; + try { + paymentData = JSON.parse(pendingPayment); + // Clear the pending payment data + localStorage.removeItem('pendingPayment'); + } catch (error) { + console.error('Error parsing pending payment data:', error); + this.setState({ + redirectUrl: '/profile#cart', + processing: false, + error: 'Invalid payment data' + }); + return; + } + + if (!paymentData.paymentId) { + console.error('No payment ID found in stored payment data'); + this.setState({ + redirectUrl: '/profile#cart', + processing: false, + error: 'Missing payment ID' + }); + return; + } + + // Check payment status with backend + this.checkMolliePaymentStatus(paymentData.paymentId); + + } catch (error) { + console.error('Error processing Mollie payment:', error); + this.setState({ + redirectUrl: '/profile#cart', + processing: false, + error: 'Payment processing failed' + }); + } + }; + + checkMolliePaymentStatus = (paymentId) => { + const { socket } = this.context; + + if (!socket || !socket.connected) { + console.error('Socket not connected'); + this.setState({ + redirectUrl: '/profile#cart', + processing: false, + error: 'Connection error' + }); + return; + } + + socket.emit('checkMollieIntent', { paymentId }, (response) => { + if (response.success) { + console.log('Payment Status:', response.payment.status); + console.log('Is Paid:', response.payment.isPaid); + console.log('Order Created:', response.order.created); + + if (response.order.orderId) { + console.log('Order ID:', response.order.orderId); + } + + // Build the redirect URL with Mollie completion parameters + const profileUrl = new URL('/profile', window.location.origin); + profileUrl.searchParams.set('mollieComplete', 'true'); + profileUrl.searchParams.set('mollie_payment_id', paymentId); + profileUrl.searchParams.set('mollie_status', response.payment.status); + profileUrl.searchParams.set('mollie_amount', response.payment.amount); + profileUrl.searchParams.set('mollie_timestamp', Date.now().toString()); + profileUrl.searchParams.set('mollie_is_paid', response.payment.isPaid.toString()); + + if (response.order.orderId) { + profileUrl.searchParams.set('mollie_order_id', response.order.orderId.toString()); + } + + // Set hash to cart tab + profileUrl.hash = '#cart'; + + this.setState({ + redirectUrl: profileUrl.pathname + profileUrl.search + profileUrl.hash, + processing: false + }); + } else { + console.error('Failed to check payment status:', response.error); + this.setState({ + redirectUrl: '/profile#cart', + processing: false, + error: response.error || 'Payment verification failed' + }); + } + }); + }; + + render() { + const { redirectUrl, processing, error } = this.state; + + if (processing) { + return ( + + + + Zahlung wird überprüft... + + + Bitte warten Sie, während wir Ihre Zahlung bei Mollie überprüfen. + + + ); + } + + if (error) { + return ( + + + Zahlungsüberprüfung fehlgeschlagen + + + {error} + + + Sie werden zu Ihrem Profil weitergeleitet... + + + ); + } + + if (redirectUrl) { + return ; + } + + // Fallback redirect to profile + return ; + } +} + +export default PaymentSuccess; \ No newline at end of file diff --git a/src/components/profile/CartTab.js b/src/components/profile/CartTab.js index e50c6be..e8f06a4 100644 --- a/src/components/profile/CartTab.js +++ b/src/components/profile/CartTab.js @@ -377,34 +377,23 @@ class CartTab extends Component { (total, item) => total + item.price * item.quantity, 0 ); - const totalAmount = Math.round((subtotal + deliveryCost) * 100); // Convert to cents + const totalAmount = Math.round((subtotal + deliveryCost) * 100) / 100; - if (this.context && this.context.socket && this.context.socket.connected) { - this.context.socket.emit( - "createMollieIntent", - { amount: totalAmount, invoiceId: 'A-34344' }, - (response) => { - if (response.success) { - - localStorage.setItem('pendingPayment', JSON.stringify({ - paymentId: response.paymentId, - amount: totalAmount, - timestamp: Date.now() - })); - window.location.href = response.checkoutUrl; - - } else { - console.error("Error:", response.error); - } - } - ); - } else { - console.error("Socket context not available"); - this.setState({ - isCompletingOrder: false, - completionError: "Cannot connect to server. Please try again later.", - }); - } + // Prepare complete order data for Mollie intent creation + const mollieOrderData = { + amount: totalAmount, + items: cartItems, + invoiceAddress, + deliveryAddress: useSameAddress ? invoiceAddress : deliveryAddress, + deliveryMethod, + paymentMethod: "mollie", + deliveryCost, + note, + domain: window.location.origin, + saveAddressForFuture, + }; + + this.orderService.createMollieIntent(mollieOrderData); return; } diff --git a/src/components/profile/OrderProcessingService.js b/src/components/profile/OrderProcessingService.js index 23ba016..3ee8e7f 100644 --- a/src/components/profile/OrderProcessingService.js +++ b/src/components/profile/OrderProcessingService.js @@ -49,18 +49,29 @@ class OrderProcessingService { waitForVerifyTokenAndProcessOrder() { // Check if window.cart is already populated (verifyToken already completed) if (Array.isArray(window.cart) && window.cart.length > 0) { - this.processStripeOrderWithCart(window.cart); + if (this.paymentCompletionData && this.paymentCompletionData.paymentType === 'mollie') { + this.processMollieOrderWithCart(window.cart); + } else { + this.processStripeOrderWithCart(window.cart); + } return; } // Listen for cart event which is dispatched after verifyToken completes this.verifyTokenHandler = () => { if (Array.isArray(window.cart) && window.cart.length > 0) { - this.processStripeOrderWithCart([...window.cart]); // Copy the cart + const cartCopy = [...window.cart]; // Copy the cart // Clear window.cart after copying window.cart = []; window.dispatchEvent(new CustomEvent("cart")); + + // Process based on payment type + if (this.paymentCompletionData && this.paymentCompletionData.paymentType === 'mollie') { + this.processMollieOrderWithCart(cartCopy); + } else { + this.processStripeOrderWithCart(cartCopy); + } } else { this.setState({ completionError: "Cart is empty. Please add items to your cart before placing an order." @@ -111,6 +122,21 @@ class OrderProcessingService { }); } + processMollieOrderWithCart(cartItems) { + // Clear timeout if it exists + if (this.verifyTokenTimeout) { + clearTimeout(this.verifyTokenTimeout); + this.verifyTokenTimeout = null; + } + + // Store cart items in state and process order + this.setState({ + originalCartItems: cartItems + }, () => { + this.processMollieOrder(); + }); + } + processStripeOrder() { // If no original cart items, don't process if (!this.getState().originalCartItems || this.getState().originalCartItems.length === 0) { @@ -205,6 +231,20 @@ class OrderProcessingService { }); } + processMollieOrder() { + // For Mollie payments, the backend handles order creation automatically + // when payment is successful. We just need to show success state. + this.setState({ + isCompletingOrder: false, + orderCompleted: true, + completionError: null, + }); + + // Clear the cart since order was created by backend + window.cart = []; + window.dispatchEvent(new CustomEvent("cart")); + } + // Process regular (non-Stripe) orders processRegularOrder(orderData) { const context = this.getContext(); @@ -271,6 +311,40 @@ class OrderProcessingService { } } + // Create Mollie payment intent + createMollieIntent(mollieOrderData) { + const context = this.getContext(); + if (context && context.socket && context.socket.connected) { + context.socket.emit( + "createMollieIntent", + mollieOrderData, + (response) => { + if (response.success) { + // Store pending payment info and redirect + localStorage.setItem('pendingPayment', JSON.stringify({ + paymentId: response.paymentId, + amount: mollieOrderData.amount, + timestamp: Date.now() + })); + window.location.href = response.checkoutUrl; + } else { + console.error("Error:", response.error); + this.setState({ + isCompletingOrder: false, + completionError: response.error || "Failed to create Mollie payment intent. Please try again.", + }); + } + } + ); + } else { + console.error("Socket context not available"); + this.setState({ + isCompletingOrder: false, + completionError: "Cannot connect to server. Please try again later.", + }); + } + } + // Calculate delivery cost getDeliveryCost() { const { deliveryMethod, paymentMethod } = this.getState(); diff --git a/src/config.js b/src/config.js index eae36db..df32320 100644 --- a/src/config.js +++ b/src/config.js @@ -1,5 +1,5 @@ const config = { - baseUrl: "https://growheads.de", + baseUrl: "https://dev.seedheads.de", apiBaseUrl: "", googleClientId: "928121624463-jbgfdlgem22scs1k9c87ucg4ffvaik6o.apps.googleusercontent.com", stripePublishableKey: "pk_test_51R7lltRtpe3h1vwJzIrDb5bcEigTLBHrtqj9SiPX7FOEATSuD6oJmKc8xpNp49ShpGJZb2GShHIUqj4zlSIz4olj00ipOuOAnu", diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 720a68d..c1848c7 100644 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -28,9 +28,11 @@ const ProfilePage = (props) => { const [orderIdFromHash, setOrderIdFromHash] = useState(null); const [paymentCompletion, setPaymentCompletion] = useState(null); - // @note Check for payment completion parameters from Stripe redirect + // @note Check for payment completion parameters from Stripe and Mollie redirects useEffect(() => { const urlParams = new URLSearchParams(location.search); + + // Check for Stripe payment completion const isComplete = urlParams.has('complete'); const paymentIntent = urlParams.get('payment_intent'); const paymentIntentClientSecret = urlParams.get('payment_intent_client_secret'); @@ -38,6 +40,7 @@ const ProfilePage = (props) => { if (isComplete && paymentIntent && redirectStatus) { setPaymentCompletion({ + paymentType: 'stripe', paymentIntent, paymentIntentClientSecret, redirectStatus, @@ -52,6 +55,39 @@ const ProfilePage = (props) => { newUrl.searchParams.delete('redirect_status'); window.history.replaceState({}, '', newUrl.toString()); } + + // Check for Mollie payment completion + const isMollieComplete = urlParams.has('mollieComplete'); + const molliePaymentId = urlParams.get('mollie_payment_id'); + const mollieStatus = urlParams.get('mollie_status'); + const mollieAmount = urlParams.get('mollie_amount'); + const mollieTimestamp = urlParams.get('mollie_timestamp'); + const mollieIsPaid = urlParams.get('mollie_is_paid'); + const mollieOrderId = urlParams.get('mollie_order_id'); + + if (isMollieComplete && molliePaymentId && mollieStatus) { + setPaymentCompletion({ + paymentType: 'mollie', + molliePaymentId, + mollieStatus, + mollieAmount: mollieAmount ? parseFloat(mollieAmount) : null, + mollieTimestamp: mollieTimestamp ? parseInt(mollieTimestamp) : null, + mollieIsPaid: mollieIsPaid === 'true', + mollieOrderId: mollieOrderId ? parseInt(mollieOrderId) : null, + isSuccessful: mollieIsPaid === 'true' + }); + + // Clean up the URL by removing the Mollie payment parameters + const newUrl = new URL(window.location); + newUrl.searchParams.delete('mollieComplete'); + newUrl.searchParams.delete('mollie_payment_id'); + newUrl.searchParams.delete('mollie_status'); + newUrl.searchParams.delete('mollie_amount'); + newUrl.searchParams.delete('mollie_timestamp'); + newUrl.searchParams.delete('mollie_is_paid'); + newUrl.searchParams.delete('mollie_order_id'); + window.history.replaceState({}, '', newUrl.toString()); + } }, [location.search]); useEffect(() => {