storeSettings = new StoreSettings(); $this->order = Arr::get($data, 'order'); $config = Arr::wrap( Arr::get($this->order, 'config') ); $rawTz = Arr::get($config, 'user_tz', 'UTC'); $this->orderTz = (@timezone_open($rawTz) !== false) ? $rawTz : 'UTC'; $orderId = Arr::get($this->order, 'id'); parent::__construct($data); } // protected array $methodMap = [ // 'customer_dashboard_link' => 'getCustomerDashboardLink', // 'payment_summary' => 'getPaymentSummary', // 'payment_receipt' => 'getPaymentReceipt', // ]; protected array $methodMap = [ 'item_count' => 'getItemCount', 'is_digital' => 'getIsDigital', 'store_vat_display' => 'getStoreVatDisplay', 'buyer_vat_display' => 'getBuyerVatDisplay', 'buyer_company_name' => 'getBuyerCompanyName', ]; protected array $attributeMap = [ 'id' => 'order.id', 'status' => 'order.status', 'created_at' => 'order.created_at', 'updated_at' => 'order.updated_at', ]; protected array $centColumns = [ 'total_amount', 'subtotal', 'discount_tax', 'manual_discount_total', 'coupon_discount_total', 'shipping_tax', 'shipping_total', 'fee_total', 'tax_total', 'total_paid', 'total_refund' ]; public function parse($accessor = '', $code = '', $transformer = null): ?string { if ($this->shouldParseAddress($accessor)) { return $this->parseAddressFields($accessor); } if (in_array($accessor, ['updated_at', 'created_at'])) { $date = Arr::get($this->data, $this->attributeMap[$accessor]); $timestamp = DateTime::anyTimeToGmt($date)->getTimestamp(); $date = wp_date( get_option('date_format'), $timestamp, new \DateTimeZone($this->orderTz) ); return Helper::translateNumber($date); } // Handle _formatted suffix for cent columns (e.g. total_amount_formatted) $formattedSuffix = '_formatted'; if (Str::endsWith($accessor, $formattedSuffix)) { $baseAccessor = substr($accessor, 0, -strlen($formattedSuffix)); if (in_array($baseAccessor, $this->centColumns)) { $amount = Arr::get($this->order, $baseAccessor); if (!is_numeric($amount)) { return (string) $amount; } return CurrencySettings::getPriceHtml($amount, $this->order['currency']); } } if (in_array($accessor, $this->centColumns)) { $amount = Arr::get($this->order, $accessor); if (!is_numeric($amount)) { return (string) $amount; } return (string) ($amount / 100); } // $html parsers $htmlParsers = [ 'order.download_details', 'order.items_table', 'order.payment_summary', 'order.payment_receipt', 'order.subscription_details', 'order.license_details', 'order.address_details', ]; if (in_array($code, $htmlParsers)) { $order = $this->order; if ($code == 'order.items_table') { return \FluentCart\App\App::make('view')->make('emails.parts.items_table', [ 'order' => $order, 'formattedItems' => $order->order_items, 'heading' => __('Order Summary', 'fluent-cart'), ]); } if ($code === 'order.subscription_details') { if ($order->subscriptions && $order->subscriptions->count() > 0) { return \FluentCart\App\App::make('view')->make('invoice.parts.subscription_items', [ 'subscriptions' => $order->subscriptions, 'order' => $order ]); } return ''; } if ($code === 'order.license_details') { $licenses = $order->getLicenses(); if ($licenses && $licenses->count() > 0) { return \FluentCart\App\App::make('view')->make('emails.parts.licenses', [ 'licenses' => $licenses, 'heading' => _n('License', 'Licenses', $licenses->count(), 'fluent-cart'), 'show_notice' => false ]); } return ''; } if ($code === 'order.download_details') { $downloads = $order->getDownloads(); if ($downloads) { return \FluentCart\App\App::make('view')->make('emails.parts.downloads', [ 'order' => $order, 'heading' => _n('Download', 'Downloads', count($downloads), 'fluent-cart'), 'downloadItems' => $downloads, ]); } return ''; } if ($code === 'order.address_details') { return \FluentCart\App\App::make('view')->make('emails.parts.addresses', [ 'order' => $order, ]); } if ($code == 'order.payment_summary') { return $this->getPaymentSummary(); } if ($code == 'order.payment_receipt') { return $this->getPaymentReceipt(); } } return $this->get($accessor, $code); } public function shouldParseAddress($accessor): bool { return Str::startsWith($accessor, 'billing.') || Str::startsWith($accessor, 'shipping.'); } public function parseAddressFields($accessor) { list($addressType, $accessorsKey) = $this->resolveAddressFieldKeys($accessor); return $this->getAddressData($addressType, $accessorsKey); } public function resolveAddressFieldKeys($accessor): array { $exploded = explode('.', $accessor); $addressType = $exploded[0]; $accessorsKey = implode('.', array_slice($exploded, 1)); return [$addressType, $accessorsKey]; } public function getAddressData($addressAccessor, $accessor = null) { $address = Arr::get($this->order, $addressAccessor . '_address'); if (empty($address)) { return ""; } $formattedFields = ['city', 'state', 'country']; if (in_array($accessor, $formattedFields) && method_exists($address, 'getFormattedAddress')) { $formatted = $address->getFormattedAddress(); return Arr::get($formatted, $accessor) ?: ''; } return Arr::get($address, $accessor) ?: ''; } public function getPaymentSummary() { $order = $this->order; return \FluentCart\App\App::make('view')->make('emails.parts.items_table', [ 'order' => $order, 'formattedItems' => $order->order_items, 'heading' => '', ]); } public function getPaymentReceipt() { $order = $this->order; ob_start(); \FluentCart\App\App::make('view')->render('emails.parts.items_table', [ 'order' => $order, 'formattedItems' => $order->order_items, 'heading' => __('Order Summary', 'fluent-cart'), ]); if ($order->subscriptions && $order->subscriptions->count() > 0) { \FluentCart\App\App::make('view')->render('invoice.parts.subscription_items', [ 'subscriptions' => $order->subscriptions, 'order' => $order ]); } $licenses = $order->getLicenses(); if ($licenses && $licenses->count() > 0) { \FluentCart\App\App::make('view')->render('emails.parts.licenses', [ 'licenses' => $licenses, 'heading' => __('Licenses', 'fluent-cart'), 'show_notice' => false ]); } $downloads = $order->getDownloads(); if ($downloads) { \FluentCart\App\App::make('view')->render('emails.parts.downloads', [ 'order' => $order, 'heading' => __('Downloads', 'fluent-cart'), 'downloadItems' => $downloads, ]); } echo '
'; \FluentCart\App\App::make('view')->render('emails.parts.addresses', [ 'order' => $order, ]); return ob_get_clean(); } public function getDiscountTotal(): string { return (string) ($this->getDiscountTotalInCents() / 100); } public function getDiscountTotalFormatted(): string { return CurrencySettings::getPriceHtml($this->getDiscountTotalInCents(), $this->order['currency']); } private function getDiscountTotalInCents(): int { return (int) Arr::get($this->order, 'coupon_discount_total', 0) + (int) Arr::get($this->order, 'manual_discount_total', 0); } public function getOrderRef(): string { $invoiceNo = Arr::get($this->order, 'invoice_no'); if (!empty($invoiceNo)) { return (string) $invoiceNo; } return (string) Arr::get($this->order, 'id'); } public function getCustomerDashboardAnchorLink($accessor, $code = null, $conditions = []) { $defaultValue = Arr::get($conditions, 'default_value') ?? Arr::get($this->order, 'invoice_no'); if (empty($this->order)) { return $code; } $profilePage = $this->storeSettings->getCustomerProfilePage(); if (!empty($profilePage)) { return "" . $defaultValue . ""; } else { return Arr::get($this->order, 'invoice_no'); } } public function getCustomerDashboardLink($accessor, $code = null) { if (empty($this->order)) { return $code; } $orderLink = TemplateService::getCustomerProfileUrl('order/' . Arr::get($this->order, 'uuid')); return is_user_logged_in() ? $orderLink : wp_login_url($orderLink); } public function getAdminOrderLink($accessor, $code = null) { if (empty($this->order)) { return $code; } return admin_url('admin.php?page=fluent-cart#/orders/' . Arr::get($this->order, 'id') . '/view'); } public function getAdminOrderAnchorLink($accessor, $code = null, $conditions = []) { $defaultValue = Arr::get($conditions, 'default_value'); if (empty($this->order)) { return $code; } $url = admin_url('admin.php?page=fluent-cart#/orders/' . Arr::get($this->order, 'id') . '/view'); if (!empty($defaultValue)) { return "" . $defaultValue . ""; } return $url; } public function getCustomerOrderLink($accessor, $code = null) { if (empty($this->order)) { return $code; } $customerProfilePage = $this->storeSettings->getCustomerProfilePage(); $orderLink = $customerProfilePage . '#/order/' . Arr::get($this->order, 'uuid'); return is_user_logged_in() ? $orderLink : wp_login_url($orderLink); } public function getTotalAmount() { $total = ($this->order['total_amount'] / 100); $currency_sign = $this->order['currency']; return $total . $currency_sign; } public function getDownloads() { $order = $this->order; $downloads = $order->getDownloads(); if ($downloads) { return (string)\FluentCart\App\App::make('view')->make('emails.parts.downloads', [ 'order' => $order, 'heading' => '', 'downloadItems' => $downloads ]); } return ''; } public function getLicenses() { $order = $this->order; $licenses = $order->getLicenses(); if ($licenses && $licenses->count() > 0) { return (string)\FluentCart\App\App::make('view')->make('emails.parts.licenses', [ 'licenses' => $licenses, 'heading' => __('Licenses', 'fluent-cart'), 'show_notice' => false ]); } return ''; } public function getLicenseCount(): string { return (string)$this->licenses->count(); } public function getIsDigital(): string { if (!$this->order) { return 'no'; } $fulfillmentType = Arr::get($this->order, 'fulfillment_type'); return $fulfillmentType === 'digital' ? 'yes' : 'no'; } public function getItemCount(): string { $orderItems = $this->order ? $this->order->order_items : null; if ($orderItems) { return (string)$orderItems->count(); } return '0'; } public function getPaymentMethodTitle(): string { if (!$this->order) { return ''; } $title = (string) Arr::get($this->order, 'payment_method_title', ''); if ($title !== '') { return $title; } $slug = (string) Arr::get($this->order, 'payment_method', ''); if ($slug === '') { return ''; } if (class_exists(GatewayManager::class)) { $gateway = GatewayManager::getInstance($slug); if ($gateway) { $gatewayTitle = (string) $gateway->getMeta('title'); if ($gatewayTitle !== '') { return $gatewayTitle; } } } return ucwords(str_replace(['_', '-'], ' ', $slug)); } public function getTaxBreakdown(): string { if (!$this->order) { return ''; } $currency = (string) Arr::get($this->order, 'currency', ''); $taxTotal = (int) Arr::get($this->order, 'tax_total', 0); $shippingTax = (int) Arr::get($this->order, 'shipping_tax', 0); $rowStyle = 'padding:5px 0;font-size:11px;'; $labelStyle = $rowStyle . 'color:#525866;'; $valueStyle = $rowStyle . 'color:#0E121B;text-align:right;'; $rows = ''; $rates = $this->order->orderTaxRates ?? null; if ($rates && $rates->count() > 0) { foreach ($rates as $rate) { $label = $this->resolveTaxRateLabel($rate); $amount = (int) Arr::get($rate, 'total_tax', (int) Arr::get($rate, 'order_tax', 0) + (int) Arr::get($rate, 'shipping_tax', 0)); $rows .= '' . '' . esc_html($label) . '' . '' . CurrencySettings::getPriceHtml($amount, $currency) . '' . ''; } return $rows; } if ($taxTotal > 0 || $shippingTax > 0) { if ($shippingTax > 0 && $taxTotal > $shippingTax) { $rows .= '' . '' . esc_html__('Tax', 'fluent-cart') . '' . '' . CurrencySettings::getPriceHtml($taxTotal - $shippingTax, $currency) . '' . ''; $rows .= '' . '' . esc_html__('Shipping Tax', 'fluent-cart') . '' . '' . CurrencySettings::getPriceHtml($shippingTax, $currency) . '' . ''; } else { $rows .= '' . '' . esc_html__('Tax', 'fluent-cart') . '' . '' . CurrencySettings::getPriceHtml($taxTotal, $currency) . '' . ''; } } return $rows; } private function resolveTaxRateLabel($orderTaxRate): string { $taxRate = $orderTaxRate->tax_rate ?? null; if ($taxRate) { $name = (string) Arr::get($taxRate, 'name', ''); $rate = Arr::get($taxRate, 'rate'); if ($name !== '' && is_numeric($rate)) { return $name . ' (' . rtrim(rtrim((string) $rate, '0'), '.') . '%)'; } if ($name !== '') { return $name; } } return __('Tax', 'fluent-cart'); } public function getSubscriptions() { return ''; } /** * Returns formatted store VAT display string, e.g. "VAT: NL123456789B01". * Returns empty string if no store VAT is configured for this order. */ public function getStoreVatDisplay(): string { if (!$this->order) { return ''; } $orderTaxRate = $this->order->orderTaxRates ? $this->order->orderTaxRates->first() : null; if (!$orderTaxRate) { return ''; } $storeVatNumber = Arr::get($orderTaxRate->meta ?? [], 'store_vat_number', ''); if (empty($storeVatNumber)) { return ''; } $taxCountry = Arr::get($orderTaxRate->meta ?? [], 'tax_country', ''); $label = \FluentCart\App\Modules\Tax\TaxModule::getCountryTaxTitle($taxCountry); return esc_html($label) . ': ' . esc_html($storeVatNumber); } /** * Returns formatted buyer VAT display string, e.g. "VAT/Tax ID: XX123456". * Checks OrderMeta vat_tax_id first, then falls back to EU VAT reverse charge number. */ public function getBuyerVatDisplay(): string { if (!$this->order) { return ''; } // Check OrderMeta vat_tax_id first (simple VAT/Tax ID) $vatMeta = OrderMeta::query() ->where('order_id', $this->order->id) ->where('meta_key', 'vat_tax_id') ->orderBy('id', 'DESC') ->first(); if ($vatMeta && !empty($vatMeta->meta_value)) { return esc_html(__('VAT/Tax ID', 'fluent-cart')) . ': ' . esc_html($vatMeta->meta_value); } // Fall back to EU VAT reverse charge number $orderTaxRate = $this->order->orderTaxRates ? $this->order->orderTaxRates->first() : null; $vatNumber = Arr::get($orderTaxRate->meta ?? [], 'vat_reverse.vat_number', ''); if (!empty($vatNumber)) { return esc_html(__('EU VAT', 'fluent-cart')) . ': ' . esc_html($vatNumber); } return ''; } /** * Returns buyer company name from billing address meta or VAT reverse charge data. */ public function getBuyerCompanyName(): string { if (!$this->order) { return ''; } // Check billing address meta first if ($this->order->billing_address) { $companyName = Arr::get($this->order->billing_address->meta ?? [], 'other_data.company_name', ''); if (!empty($companyName)) { return esc_html($companyName); } } // Fall back to VAT reverse charge name $orderTaxRate = $this->order->orderTaxRates ? $this->order->orderTaxRates->first() : null; return esc_html(Arr::get($orderTaxRate->meta ?? [], 'vat_reverse.name', '')); } }