PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 All 49 releases
← All changes | app/Models/Order.php +51 -2 1.6.3 → 1.6.5 View file →
@@ -772,9 +772,18 @@
772 772 }
773 773
774 774 public function getBusinessInfoAttribute(): array
775 775 {
776 - return $this->getBusinessInfo();
776 + $businessInfo = $this->getBusinessInfo();
777 +
778 + // Only meaningful for a MoR reverse-charge order (no orderTaxRate row) — omit
779 + // for every normal order instead of appending a redundant duplicate of total_paid.
780 + $morVatRemoved = $this->getMoRVatRemovedAmount();
781 + if ($morVatRemoved > 0) {
782 + $businessInfo['net_total_paid'] = $this->netAmount((int) $this->total_paid);
783 + }
784 +
785 + return $businessInfo;
777 786 }
778 787
779 788 public function getIsReverseChargeTaxOrderAttribute(): bool
780 789 {
@@ -814,8 +823,40 @@
814 823 {
815 824 return $this->transactions()->where('status', Status::TRANSACTION_SUCCEEDED)->sum('total');
816 825 }
817 826
827 + /**
828 + * Amount of VAT removed by a merchant-of-record gateway (e.g. Paddle) for a
829 + * reverse-charge order that never ran the tax module (no `fct_order_tax_rate` row).
830 + * Display-only — `total_amount`/`total_paid`/`txn.total` stay gross for such orders
831 + * (cover invariant: the "fully paid" equality that drives due-amount checks, digital
832 + * auto-complete, and dunning reminders depends on it), so this is never subtracted
833 + * into the ledger, only into `getDisplayTotalPaid()` for the admin UI. Zero for
834 + * core-handled reverse charge (tax-rate row present) — those are already net at
835 + * creation time.
836 + */
837 + public function getMoRVatRemovedAmount(): int
838 + {
839 + if ($this->getPrimaryOrderTaxRate()) {
840 + return 0;
841 + }
842 +
843 + return (int) Arr::get($this->getBusinessInfo(), 'mor_vat_removed', 0);
844 + }
845 +
846 + /**
847 + * Nets a raw gross ledger figure (total_paid, a live paid-total sum, a refund amount)
848 + * by the MoR VAT removal — see getMoRVatRemovedAmount(). Single formula for every
849 + * "what did we actually collect/need to refund" comparison, so callers never
850 + * reimplement the subtraction themselves. Never use for due-amount, digital
851 + * auto-complete, or reminder logic; those must keep comparing the gross ledger
852 + * columns (total_amount vs total_paid) so the "fully paid" equality still holds.
853 + */
854 + public function netAmount(int $amount): int
855 + {
856 + return max(0, $amount - $this->getMoRVatRemovedAmount());
857 + }
858 +
818 859 public function getTotalRefundAmount()
819 860 {
820 861 return $this->transactions()->where('status', Status::TRANSACTION_REFUNDED)->sum('total');
821 862 }
@@ -826,9 +867,13 @@
826 867 $totalRefunded = $this->getTotalRefundAmount();
827 868
828 869 $this->total_refund = $totalRefunded;
829 870
830 - if (floatval($totalRefunded) >= floatval($totalPaid)) {
871 + // Net out MoR VAT removal so a full refund of the actually captured amount
872 + // resolves to "fully refunded" — see netAmount().
873 + $netTotalPaid = $this->netAmount($totalPaid);
874 +
875 + if (floatval($totalRefunded) >= floatval($netTotalPaid)) {
831 876 $this->payment_status = Status::PAYMENT_REFUNDED;
832 877 } elseif ($totalPaid > $totalRefunded) {
833 878 $this->payment_status = Status::PAYMENT_PARTIALLY_REFUNDED;
834 879 }
@@ -842,8 +887,12 @@
842 887 {
843 888 $paymentStatus = $type == 'full' ? Status::PAYMENT_REFUNDED : Status::PAYMENT_PARTIALLY_REFUNDED;
844 889 $this->total_refund += $refundedAmount;
845 890 $this->payment_status = $paymentStatus;
891 +
892 + if ($paymentStatus === Status::PAYMENT_REFUNDED && !$this->refunded_at) {
893 + $this->refunded_at = DateTime::gmtNow();
894 + }
846 895
847 896 $this->save();
848 897
849 898 return $this;