'double', 'cost' => 'double', 'subtotal' => 'double', 'tax_amount' => 'double', 'shipping_charge' => 'double', 'discount_total' => 'double', 'line_total' => 'double', 'refund_total' => 'double', ]; protected static function booted() { static::retrieved(function ($product) { $product->append('formatted_total'); }); } protected function getFormattedTotalAttribute() { if ($this->line_total == 0 && $this->discount_total == 0) { return Helper::toDecimal($this->subtotal); } return Helper::toDecimal($this->line_total); } public function getCouponDiscountAttribute() { return (int)($this->line_meta['coupon_discount'] ?? 0); } public function setOtherInfoAttribute($value) { if (is_array($value) || is_object($value)) { $this->attributes['other_info'] = json_encode($value); } else { $this->attributes['other_info'] = $value; } } public function getOtherInfoAttribute($value) { if (is_string($value)) { $decoded = json_decode($value, true); return is_array($decoded) ? $decoded : []; } return []; } public function setLineMetaAttribute($value) { if (is_array($value) || is_object($value)) { $this->attributes['line_meta'] = json_encode($value); } else { $this->attributes['line_meta'] = []; } } public function getLineMetaAttribute($value) { if (is_string($value)) { $decoded = json_decode($value, true); if ($decoded) { return $decoded; } } return []; } public function getFullNameAttribute($value) { return $this->title . ' ' . $this->post_title; } public function order() { return $this->belongsTo(Order::class, 'order_id', 'id'); } public function product() { return $this->belongsTo(Product::class, 'post_id', 'ID'); } public function variants() { return $this->belongsTo(ProductVariation::class, 'object_id', 'id'); } public function product_downloads() { return $this->belongsTo(ProductDownload::class, 'post_id', 'post_id'); } public function createItem($orderItems) { return $this->belongsTo(ProductVariation::class, 'variation_id', 'id'); } public function processCustom($product, $orderId) { return (new OrderItemHelper())->processCustom($product, $orderId); } /** * Get subscription payment info if available * * @return string */ public function getPaymentInfoAttribute(): string { return $this->getSubscriptionInfo('payment'); } /** * Get subscription setup info if available * * @return string */ public function getSetupInfoAttribute(): string { return $this->getSubscriptionInfo('setup'); } /** * Helper method to get subscription info * * @param string $type 'payment' or 'setup' * @return string */ private function getSubscriptionInfo(string $type): string { if ($this->payment_type !== 'subscription') { return ''; } $otherInfo = $this->other_info ?? []; $unitPrice = $this->unit_price ?? 0; if ($type === 'payment') { return Helper::generateSubscriptionInfo($otherInfo, $unitPrice) ?? ''; } if ($type === 'setup') { return Helper::generateSetupFeeInfo($otherInfo) ?? ''; } return ''; } public function productImage(): HasOne { return $this->hasOne(PostMeta::class, 'post_id', 'post_id') ->where('postmeta.meta_key', 'fluent-products-gallery-image'); } public function variantImages(): HasOne { return $this->hasOne(ProductMeta::class, 'object_id', 'object_id') ->where('object_type', 'product_variant_info') ->where('meta_key', 'product_thumbnail'); } public function getIsCustomAttribute() { return $this->other_info['is_custom'] ?? false; } public function getViewUrlAttribute() { return $this->is_custom ? ($this->other_info['view_url'] ?? '') : ''; } public function getDisplayTitle() { if ($this->post_title == $this->title) { return $this->title; } return $this->post_title . ' - ' . $this->title; } /** * Labeled attribute combination resolved from the frozen * other_info['item_attributes'] snapshot, e.g. "Color: Red | Size: XS". * Falls back to the stored variation title when no attribute snapshot * resolves (custom/simple items, or attrs whose groups were removed). * * @return string */ public function getVariationDisplayTitleAttribute() { // Order items carry the item_attributes snapshot (written at order // creation), so resolve directly — getDisplayAttributesString renders // the labeled combination and falls back to the variation title. return \FluentCart\App\Helpers\AttributeHelper::getDisplayAttributesString( Arr::get($this->other_info, 'item_attributes', []), $this, 'order_item' ); } }