CMXX - Checkout and Placeorder

This commit is contained in:
“VeLiTi”
2025-02-17 19:01:04 +01:00
parent 2072250072
commit 3aaa6c6680
26 changed files with 3148 additions and 4 deletions

View File

@@ -3086,4 +3086,411 @@ function calculateTotalPrice($product_data, $selected_options) {
'total_price' => $total_price,
'selected_items' => implode(', ', $selected_item_names)
];
}
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
// ShoppingCartCalulator ++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
class ShoppingCartCalculator {
private $products;
private $selected_country;
private $selected_shipping_method;
private $business_type;
private $discount_code;
private $db;
private $discount_message;
private $tax_rate;
public function __construct($products, $selected_country, $selected_shipping_method, $business_type, $discount_code, $db) {
$this->products = $products;
$this->selected_country = $selected_country;
$this->selected_shipping_method = $selected_shipping_method;
$this->business_type = strtolower($business_type);
$this->discount_code = $discount_code;
$this->db = $db;
$this->discount_message = '';
$this->tax_rate = $this->getTaxRate();
}
public function calculateTotals() {
// Calculate basic totals
$subtotal = $this->calculateSubtotal();
$weighttotal = $this->calculateWeightTotal();
$shippingtotal = $this->calculateShippingTotal($subtotal, $weighttotal,$this->selected_shipping_method);
$discounttotal = $this->calculateDiscountTotal();
$taxtotal = $this->calculateTaxTotal($subtotal - $discounttotal + $shippingtotal);
// Calculate final total based on business type
$total = $this->calculateFinalTotal($subtotal, $shippingtotal, $discounttotal, $taxtotal);
return [
'cart_details' => [
'products' => $this->products,
'selected_country' => $this->selected_country,
'selected_shipping_method' => $this->selected_shipping_method,
'business_type' => $this->business_type,
'discount_code' => $this->discount_code
],
'totals' => [
'subtotal' => number_format($subtotal, 2, '.', ''),
'weighttotal' => number_format($weighttotal, 2, '.', ''),
'shippingtotal' => number_format($shippingtotal, 2, '.', ''),
'discounttotal' => number_format($discounttotal, 2, '.', ''),
'discount_message' => $this->discount_message,
'tax_rate' => number_format($this->tax_rate, 2, '.', '') . '%',
'taxtotal' => number_format($taxtotal, 2, '.', ''),
'total' => number_format($total, 2, '.', '')
]
];
}
private function getTaxRate() {
$sql = "SELECT rate FROM taxes WHERE country = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$this->selected_country]);
$tax = $stmt->fetch(PDO::FETCH_ASSOC);
return $tax ? floatval($tax['rate']) : 0;
}
private function calculateSubtotal() {
$subtotal = 0;
foreach ($this->products as $product) {
$product_price = floatval(str_replace(',', '.', $product['options_price']));
$subtotal += $product_price * $product['quantity'];
}
return $subtotal;
}
private function calculateWeightTotal() {
$weighttotal = 0;
foreach ($this->products as $product) {
$options_weight = floatval($product['options_weight']);
$weighttotal += $options_weight * $product['quantity'];
}
return $weighttotal;
}
private function calculateDiscountTotal() {
if (empty($this->discount_code)) {
$this->discount_message = '';
return 0;
}
$current_date = date('Y-m-d H:i:s');
// First check if discount code exists and is valid
$sql = "SELECT * FROM discounts WHERE discount_code = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$this->discount_code]);
$discount = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$discount) {
$this->discount_message = 'Invalid discount code';
return 0;
}
// Check date validity
if ($current_date < $discount['start_date']) {
$this->discount_message = 'Discount code not yet active';
return 0;
}
if ($current_date > $discount['end_date']) {
$this->discount_message = 'Discount code expired';
return 0;
}
// Convert string of IDs to arrays
$discount_product_ids = !empty($discount['product_ids']) ?
array_map('trim', explode(',', $discount['product_ids'])) : [];
$discount_category_ids = !empty($discount['category_ids']) ?
array_map('trim', explode(',', $discount['category_ids'])) : [];
$discounttotal = 0;
$eligible_products_found = false;
$total_eligible_price = 0;
// Calculate total eligible price
foreach ($this->products as $product) {
if ($this->isProductEligibleForDiscount($product, $discount_product_ids, $discount_category_ids)) {
$eligible_products_found = true;
$product_price = floatval(str_replace(',', '.', $product['options_price'])) * $product['quantity'];
$total_eligible_price += $product_price;
}
}
// Calculate discount if eligible products found
if ($eligible_products_found) {
if ($discount['discount_type'] == 1) {
// Percentage discount
$discounttotal = $total_eligible_price * ($discount['discount_value'] / 100);
} else {
// Fixed amount discount
$discounttotal = min($discount['discount_value'], $total_eligible_price);
}
$discount_type = $discount['discount_type'] == 1 ?
$discount['discount_value'] . '% discount' :
'€' . number_format($discount['discount_value'], 2) . ' discount';
$this->discount_message = "Discount applied successfully: " . $discount_type;
} else {
$this->discount_message = 'No eligible products for this discount code';
$discounttotal = 0;
}
return $discounttotal;
}
private function isProductEligibleForDiscount($product, $discount_product_ids, $discount_category_ids) {
// If no specific products or categories are set, discount applies to all products
if (empty($discount_product_ids) && empty($discount_category_ids)) {
return true;
}
$product_match = false;
$category_match = false;
// Check product ID match
if (!empty($discount_product_ids)) {
$product_match = in_array($product['id'], $discount_product_ids);
// If only product IDs are specified (no categories), return the product match result
if (empty($discount_category_ids)) {
return $product_match;
}
} else {
// If no product IDs specified, set product_match to true
$product_match = true;
}
// Check category match
if (!empty($discount_category_ids)) {
if (isset($product['meta']['category_ids'])) {
$product_categories = is_array($product['meta']['category_ids']) ?
$product['meta']['category_ids'] :
array_map('trim', explode(',', $product['meta']['category_ids']));
$category_match = !empty(array_intersect($product_categories, $discount_category_ids));
} else {
$category_match = false;
}
// If only categories are specified (no products), return the category match result
if (empty($discount_product_ids)) {
return $category_match;
}
} else {
// If no categories specified, set category_match to true
$category_match = true;
}
// If both product IDs and categories are specified, both must match
return $product_match && $category_match;
}
private function calculateShippingTotal($subtotal, $weighttotal,$selected_shipping_method) {
//USER PROVIDED SHIPMENT METHOD
$sql = "SELECT price FROM shipping WHERE ID = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$this->selected_shipping_method]);
$shipping = $stmt->fetch(PDO::FETCH_ASSOC);
return $shipping ? floatval($shipping['price']) : 0;
}
private function calculateTaxTotal($amount_to_tax) {
$sql = "SELECT rate FROM taxes WHERE country = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$this->selected_country]);
$tax = $stmt->fetch(PDO::FETCH_ASSOC);
return $tax ? ($amount_to_tax * ($tax['rate'] / 100)) : 0;
}
private function calculateFinalTotal($subtotal, $shippingtotal, $discounttotal, $taxtotal) {
$base = $subtotal - $discounttotal + $shippingtotal;
if ($this->business_type === 'b2c') {
// Tax is included in final price
return $base;
} else {
// Tax is added on top for B2B
return $base + $taxtotal;
}
}
}
function validateCheckoutData($post_content) {
$errors = [];
// Required fields for checkout input
$required_checkout_fields = [
'cart' => 'Products',
'checkout_input.selected_country' => 'Country',
'checkout_input.selected_shipment_method' => 'Shipping method',
'checkout_input.business_type' => 'Business type',
'checkout_input.payment_method' => 'Payment method'
];
// Required fields for customer details
$required_customer_fields = [
'customer_details.email' => 'Email',
'customer_details.first_name' => 'First name',
'customer_details.last_name' => 'Last name',
'customer_details.address_street' => 'Street address',
'customer_details.address_city' => 'City',
'customer_details.address_zip' => 'ZIP code',
'customer_details.address_country' => 'Country',
'customer_details.address_phone' => 'Phone number'
];
// Validate checkout input fields
foreach ($required_checkout_fields as $field => $label) {
$keys = explode('.', $field);
if (count($keys) === 1) {
if (!isset($post_content[$keys[0]]) || empty($post_content[$keys[0]])) {
$errors[] = "$label is required";
}
} else {
if (!isset($post_content[$keys[0]][$keys[1]]) || empty($post_content[$keys[0]][$keys[1]])) {
$errors[] = "$label is required";
}
}
}
// Validate customer details fields
foreach ($required_customer_fields as $field => $label) {
$keys = explode('.', $field);
if (!isset($post_content[$keys[0]][$keys[1]]) || empty($post_content[$keys[0]][$keys[1]])) {
$errors[] = "$label is required";
}
}
// Additional validation for email format
if (isset($post_content['customer_details']['email']) && !empty($post_content['customer_details']['email'])) {
if (!filter_var($post_content['customer_details']['email'], FILTER_VALIDATE_EMAIL)) {
$errors[] = "Invalid email format";
}
}
// Additional validation for phone number (basic format check)
if (isset($post_content['customer_details']['address_phone']) && !empty($post_content['customer_details']['address_phone'])) {
if (!preg_match("/^[0-9\-\(\)\/\+\s]*$/", $post_content['customer_details']['address_phone'])) {
$errors[] = "Invalid phone number format";
}
}
return $errors;
}
function validateTransactionData($post_content) {
$errors = [];
// Required fields for customer details
$required_fields = [
'customer_details.email' => 'Email',
'customer_details.first_name' => 'First name',
'customer_details.last_name' => 'Last name',
'customer_details.address_street' => 'Street address',
'customer_details.address_city' => 'City',
'customer_details.address_zip' => 'ZIP code',
'customer_details.address_country' => 'Country',
'total.payment_amount' => 'Payment_amount',
];
// Validate customer details fields
foreach ($required_fields as $field => $label) {
$keys = explode('.', $field);
if (!isset($post_content[$keys[0]][$keys[1]]) || empty($post_content[$keys[0]][$keys[1]])) {
$errors[] = "$label is required";
}
}
return $errors;
}
function getCountryNamesByIds($countries, $idString) {
// Create a lookup array where ID is the key and country name is the value
$countryMap = array_column($countries, 'country', 'id');
// Convert comma-separated string to array
$ids = explode(',', $idString);
// Get country names for each ID
$countryNames = [];
foreach ($ids as $id) {
$id = trim($id);
if (isset($countryMap[$id])) {
$countryNames[] = $countryMap[$id];
}
}
return $countryNames;
}
function transformOrderData(array $orderData): array {
// Initialize the result array with the first row's common data
$firstRow = $orderData[0];
$result = [
'customer' => [
'email' => $firstRow['payer_email'],
'name' => $firstRow['first_name'] . ' ' . $firstRow['last_name'],
'street' => $firstRow['address_street'],
'zip' => $firstRow['address_zip'],
'city' => $firstRow['address_city'],
'country' => $firstRow['address_country']
],
'products' => [],
'invoice' => [
'id' => $firstRow['invoice'],
'created' => $firstRow['invoice_created'],
'payment_status' => $firstRow['payment_status']
],
'pricing' => [
'subtotal' => 0,
'shipping_total' => $firstRow['shipping_amount'],
'tax_total' => $firstRow['tax_amount'],
'discount_total' => $firstRow['discount_amount'],
'payment_amount' => $firstRow['payment_amount']
]
];
// Process products from all rows
foreach ($orderData as $row) {
// Decode JSON string for item options
$itemOptions = json_decode($row['item_options'], true) ?? [];
// Calculate line total
$lineTotal = floatval($row['item_price']) * intval($row['item_quantity']);
// Add to subtotal
$result['pricing']['subtotal'] += $lineTotal;
// Add product information
$result['products'][] = [
'item_id' => $row['item_id'],
'product_name' => $row['productname'],
'options' => $itemOptions,
'quantity' => $row['item_quantity'],
'price' => $row['item_price'],
'line_total' => number_format($lineTotal, 2, '.', '')
];
}
// Format monetary values
$result['pricing']['subtotal'] = number_format($result['pricing']['subtotal'], 2, '.', '');
$result['pricing']['shipping_total'] = number_format(floatval($result['pricing']['shipping_total']), 2, '.', '');
$result['pricing']['tax_total'] = number_format(floatval($result['pricing']['tax_total']), 2, '.', '');
$result['pricing']['discount_total'] = number_format(floatval($result['pricing']['discount_total']), 2, '.', '');
$result['pricing']['payment_amount'] = number_format(floatval($result['pricing']['payment_amount']), 2, '.', '');
return $result;
}