372 lines
11 KiB
JavaScript
372 lines
11 KiB
JavaScript
import { isUserLoggedIn } from "../LoginComponent.js";
|
|
|
|
class OrderProcessingService {
|
|
constructor(getContext, setState) {
|
|
this.getContext = getContext;
|
|
this.setState = setState;
|
|
this.verifyTokenHandler = null;
|
|
this.verifyTokenTimeout = null;
|
|
this.socketHandler = null;
|
|
this.paymentCompletionData = null;
|
|
}
|
|
|
|
// Clean up all event listeners and timeouts
|
|
cleanup() {
|
|
if (this.verifyTokenHandler) {
|
|
window.removeEventListener('cart', this.verifyTokenHandler);
|
|
this.verifyTokenHandler = null;
|
|
}
|
|
if (this.verifyTokenTimeout) {
|
|
clearTimeout(this.verifyTokenTimeout);
|
|
this.verifyTokenTimeout = null;
|
|
}
|
|
if (this.socketHandler) {
|
|
window.removeEventListener('cart', this.socketHandler);
|
|
this.socketHandler = null;
|
|
}
|
|
}
|
|
|
|
// Handle payment completion from parent component
|
|
handlePaymentCompletion(paymentCompletion, onClearPaymentCompletion) {
|
|
// Store payment completion data before clearing
|
|
this.paymentCompletionData = { ...paymentCompletion };
|
|
|
|
// Clear payment completion data to prevent duplicates
|
|
if (onClearPaymentCompletion) {
|
|
onClearPaymentCompletion();
|
|
}
|
|
|
|
// Show payment confirmation immediately but wait for verifyToken to complete
|
|
this.setState({
|
|
showPaymentConfirmation: true,
|
|
cartItems: [] // Clear UI cart immediately
|
|
});
|
|
|
|
// Wait for verifyToken to complete and populate window.cart, then process order
|
|
this.waitForVerifyTokenAndProcessOrder();
|
|
}
|
|
|
|
waitForVerifyTokenAndProcessOrder() {
|
|
// Check if window.cart is already populated (verifyToken already completed)
|
|
if (Array.isArray(window.cart) && window.cart.length > 0) {
|
|
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) {
|
|
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."
|
|
});
|
|
}
|
|
|
|
// Clean up listener
|
|
if (this.verifyTokenHandler) {
|
|
window.removeEventListener('cart', this.verifyTokenHandler);
|
|
this.verifyTokenHandler = null;
|
|
}
|
|
};
|
|
|
|
window.addEventListener('cart', this.verifyTokenHandler);
|
|
|
|
// Set up a timeout as fallback (in case verifyToken fails)
|
|
this.verifyTokenTimeout = setTimeout(() => {
|
|
if (Array.isArray(window.cart) && window.cart.length > 0) {
|
|
this.processStripeOrderWithCart([...window.cart]);
|
|
window.cart = [];
|
|
window.dispatchEvent(new CustomEvent("cart"));
|
|
} else {
|
|
this.setState({
|
|
completionError: "Unable to load cart data. Please refresh the page and try again."
|
|
});
|
|
}
|
|
|
|
// Clean up
|
|
if (this.verifyTokenHandler) {
|
|
window.removeEventListener('cart', this.verifyTokenHandler);
|
|
this.verifyTokenHandler = null;
|
|
}
|
|
}, 5000); // 5 second timeout
|
|
}
|
|
|
|
processStripeOrderWithCart(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.processStripeOrder();
|
|
});
|
|
}
|
|
|
|
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) {
|
|
this.setState({ completionError: "Cart is empty. Please add items to your cart before placing an order." });
|
|
return;
|
|
}
|
|
|
|
// If socket is ready, process immediately
|
|
|
|
const { isLoggedIn: isAuthenticated } = isUserLoggedIn();
|
|
if (isAuthenticated) {
|
|
this.sendStripeOrder();
|
|
return;
|
|
}
|
|
|
|
|
|
// Wait for socket to be ready
|
|
this.socketHandler = () => {
|
|
|
|
const { isLoggedIn: isAuthenticated } = isUserLoggedIn();
|
|
const state = this.getState();
|
|
|
|
if (isAuthenticated && state.showPaymentConfirmation && !state.isCompletingOrder) {
|
|
this.sendStripeOrder();
|
|
}
|
|
|
|
// Clean up
|
|
if (this.socketHandler) {
|
|
window.removeEventListener('cart', this.socketHandler);
|
|
this.socketHandler = null;
|
|
}
|
|
};
|
|
window.addEventListener('cart', this.socketHandler);
|
|
}
|
|
|
|
sendStripeOrder() {
|
|
const state = this.getState();
|
|
|
|
// Don't process if already processing or completed
|
|
if (state.isCompletingOrder || state.orderCompleted) {
|
|
return;
|
|
}
|
|
|
|
this.setState({ isCompletingOrder: true, completionError: null });
|
|
|
|
const {
|
|
deliveryMethod,
|
|
invoiceAddress,
|
|
deliveryAddress,
|
|
useSameAddress,
|
|
originalCartItems,
|
|
note,
|
|
saveAddressForFuture,
|
|
} = state;
|
|
|
|
const deliveryCost = this.getDeliveryCost();
|
|
|
|
const orderData = {
|
|
items: originalCartItems,
|
|
invoiceAddress,
|
|
deliveryAddress: useSameAddress ? invoiceAddress : deliveryAddress,
|
|
deliveryMethod,
|
|
paymentMethod: "stripe",
|
|
deliveryCost,
|
|
note,
|
|
domain: window.location.origin,
|
|
stripeData: this.paymentCompletionData ? {
|
|
paymentIntent: this.paymentCompletionData.paymentIntent,
|
|
paymentIntentClientSecret: this.paymentCompletionData.paymentIntentClientSecret,
|
|
redirectStatus: this.paymentCompletionData.redirectStatus,
|
|
} : null,
|
|
saveAddressForFuture,
|
|
};
|
|
|
|
|
|
window.socketManager.emit("issueStripeOrder", orderData, (response) => {
|
|
if (response.success) {
|
|
this.setState({
|
|
isCompletingOrder: false,
|
|
orderCompleted: true,
|
|
completionError: null,
|
|
});
|
|
} else {
|
|
this.setState({
|
|
isCompletingOrder: false,
|
|
completionError: response.error || "Failed to complete order. Please try again.",
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
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) {
|
|
|
|
window.socketManager.emit("issueOrder", orderData, (response) => {
|
|
if (response.success) {
|
|
// Clear the cart
|
|
window.cart = [];
|
|
window.dispatchEvent(new CustomEvent("cart"));
|
|
|
|
// Reset state and navigate to orders tab
|
|
this.setState({
|
|
isCheckingOut: false,
|
|
cartItems: [],
|
|
isCompletingOrder: false,
|
|
completionError: null,
|
|
});
|
|
|
|
// Call success callback if provided
|
|
if (this.onOrderSuccess) {
|
|
this.onOrderSuccess();
|
|
}
|
|
} else {
|
|
this.setState({
|
|
isCompletingOrder: false,
|
|
completionError: response.error || "Failed to complete order. Please try again.",
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Create Stripe payment intent
|
|
createStripeIntent(totalAmount, loadStripeComponent) {
|
|
|
|
window.socketManager.emit(
|
|
"createStripeIntent",
|
|
{ amount: totalAmount },
|
|
(response) => {
|
|
if (response.success) {
|
|
loadStripeComponent(response.client_secret);
|
|
} else {
|
|
console.error("Error:", response.error);
|
|
this.setState({
|
|
isCompletingOrder: false,
|
|
completionError: response.error || "Failed to create Stripe payment intent. Please try again.",
|
|
});
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
|
|
|
|
// Create Mollie payment intent
|
|
createMollieIntent(mollieOrderData) {
|
|
window.socketManager.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.",
|
|
});
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
// Calculate delivery cost
|
|
getDeliveryCost() {
|
|
const { deliveryMethod, paymentMethod, cartItems } = this.getState();
|
|
let cost = 0;
|
|
|
|
switch (deliveryMethod) {
|
|
case "DHL":
|
|
cost = 6.99;
|
|
break;
|
|
case "DPD":
|
|
cost = 4.9;
|
|
break;
|
|
case "Sperrgut":
|
|
cost = 28.99;
|
|
break;
|
|
case "Abholung":
|
|
cost = 0;
|
|
break;
|
|
default:
|
|
cost = 6.99;
|
|
}
|
|
|
|
// Check for free shipping threshold (>= 100€ cart value)
|
|
// Free shipping applies to DHL, DPD, and Sperrgut deliveries when cart value >= 100€
|
|
if (cartItems && Array.isArray(cartItems) && deliveryMethod !== "Abholung") {
|
|
const cartValue = cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
|
|
if (cartValue >= 100) {
|
|
cost = 0; // Free shipping for orders >= 100€
|
|
}
|
|
}
|
|
|
|
// Add onDelivery surcharge if selected (still applies even with free shipping)
|
|
if (paymentMethod === "onDelivery") {
|
|
cost += 8.99;
|
|
}
|
|
|
|
return cost;
|
|
}
|
|
|
|
// Helper method to get current state (to be overridden by component)
|
|
getState() {
|
|
throw new Error("getState method must be implemented by the component");
|
|
}
|
|
|
|
// Set callback for order success
|
|
setOrderSuccessCallback(callback) {
|
|
this.onOrderSuccess = callback;
|
|
}
|
|
}
|
|
|
|
export default OrderProcessingService;
|