PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.6
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.6
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/Modules/PaymentMethods/PayPalGateway/Processor.php +55 -29 1.6.2 → 1.6.6 View file →
@@ -79,14 +79,15 @@
79 79 {
80 80 $transaction = $paymentInstance->transaction;
81 81 $order = $paymentInstance->order;
82 82
83 + $currency = $transaction->currency;
83 84 $itemsSubTotal = 0;
84 85 $formattedItems = [];
85 86
86 87 foreach ($order->order_items as $item) {
87 88 $quantity = $item->quantity ?? 1;
88 - $perQuantity = $this->toDecimal($item->line_total / $quantity);
89 + $perQuantity = $this->toDecimal($item->line_total / $quantity, $currency);
89 90 $title = $item->post_title . ' ' . $item->title;
90 91
91 92 $formattedItems[] = [
92 93 'name' => strlen($title) > 127 ? substr($title, 0, 120) . '...' : $title,
@@ -91,10 +92,10 @@
91 92 $formattedItems[] = [
92 93 'name' => strlen($title) > 127 ? substr($title, 0, 120) . '...' : $title,
93 94 'description' => strlen($title) > 4000 ? substr($title, 0, 3997) . '...' : $title,
94 95 'unit_amount' => [
95 - 'currency_code' => $transaction->currency,
96 - 'value' => number_format($perQuantity, 2, '.', ''),
96 + 'currency_code' => $currency,
97 + 'value' => PayPalHelper::formatDecimalAmount($perQuantity, $currency),
97 98 ],
98 99 'quantity' => $quantity,
99 100 ];
100 101
@@ -100,9 +101,9 @@
100 101
101 102 $itemsSubTotal += $perQuantity * $quantity;
102 103 }
103 104
104 - $chargingAmount = $this->toDecimal($transaction->total);
105 + $chargingAmount = $this->toDecimal($transaction->total, $currency);
105 106 $pushedTotal = $itemsSubTotal;
106 107
107 108
108 109 // Learn more at: https://developer.paypal.com/docs/api/orders/v2/#definition-purchase_unit
@@ -108,14 +109,14 @@
108 109 // Learn more at: https://developer.paypal.com/docs/api/orders/v2/#definition-purchase_unit
109 110 $purchaseUnits = [
110 111 'reference_id' => $transaction->uuid, // This is the order UUID
111 112 'amount' => [ // https://developer.paypal.com/docs/api/orders/v2/#definition-amount_breakdown
112 - 'currency_code' => $transaction->currency,
113 - 'value' => number_format($chargingAmount, 2, '.', ''),
113 + 'currency_code' => $currency,
114 + 'value' => PayPalHelper::formatDecimalAmount($chargingAmount, $currency),
114 115 'breakdown' => [
115 116 'item_total' => [
116 - 'currency_code' => $transaction->currency,
117 - 'value' => number_format($itemsSubTotal, 2, '.', ''),
117 + 'currency_code' => $currency,
118 + 'value' => PayPalHelper::formatDecimalAmount($itemsSubTotal, $currency),
118 119 ]
119 120 ]
120 121 ],
121 122 'items' => $formattedItems
@@ -132,12 +133,12 @@
132 133 }
133 134 }
134 135
135 136 if ($order->shipping_total > 0) {
136 - $shippingAmount = $this->toDecimal($order->shipping_total);
137 + $shippingAmount = $this->toDecimal($order->shipping_total, $currency);
137 138 $purchaseUnits['amount']['breakdown']['shipping'] = [
138 - 'currency_code' => $transaction->currency,
139 - 'value' => number_format($shippingAmount, 2, '.', ''),
139 + 'currency_code' => $currency,
140 + 'value' => PayPalHelper::formatDecimalAmount($shippingAmount, $currency),
140 141 ];
141 142 $pushedTotal += $shippingAmount;
142 143 }
143 144
@@ -155,16 +156,16 @@
155 156
156 157 if ($taxBehavior === 1) {
157 158 // Pure exclusive: all tax is additive on top of item prices.
158 159 // tax_total includes product + fee tax (both exclusive).
159 - $taxTotal = $this->toDecimal($order->tax_total) + $this->toDecimal($order->shipping_tax);
160 + $taxTotal = $this->toDecimal($order->tax_total, $currency) + $this->toDecimal($order->shipping_tax, $currency);
160 161 } elseif ($taxBehavior === 3) {
161 162 // Mixed: only exclusive product + fee tax is additive; shipping conditional.
162 - $taxTotal = $this->toDecimal($exclusiveTaxTotal);
163 + $taxTotal = $this->toDecimal($exclusiveTaxTotal, $currency);
163 164 if ($storeTaxBehavior === 1) {
164 165 // Store is exclusive: fees and shipping are also exclusive.
165 - $taxTotal += $this->toDecimal($order->shipping_tax);
166 - $taxTotal += $this->toDecimal($feeTax);
166 + $taxTotal += $this->toDecimal($order->shipping_tax, $currency);
167 + $taxTotal += $this->toDecimal($feeTax, $currency);
167 168 }
168 169 } else {
169 170 $taxTotal = 0;
170 171 }
@@ -170,10 +171,10 @@
170 171 }
171 172
172 173 if ($taxTotal > 0) {
173 174 $purchaseUnits['amount']['breakdown']['tax_total'] = [
174 - 'currency_code' => $transaction->currency,
175 - 'value' => number_format($taxTotal, 2, '.', ''),
175 + 'currency_code' => $currency,
176 + 'value' => PayPalHelper::formatDecimalAmount($taxTotal, $currency),
176 177 ];
177 178 $pushedTotal += $taxTotal;
178 179 }
179 180
@@ -179,10 +180,10 @@
179 180
180 181 if ($chargingAmount < $pushedTotal) {
181 182 $discount = $pushedTotal - $chargingAmount;
182 183 $purchaseUnits['amount']['breakdown']['discount'] = [
183 - 'currency_code' => $transaction->currency,
184 - 'value' => number_format($discount, 2, '.', ''),
184 + 'currency_code' => $currency,
185 + 'value' => PayPalHelper::formatDecimalAmount($discount, $currency),
185 186 ];
186 187 } else if ($chargingAmount > $pushedTotal) {
187 188 $extraChargeNeedToBeAdded = $chargingAmount - $pushedTotal;
188 189 $formattedItems[] = [
@@ -187,10 +188,10 @@
187 188 $extraChargeNeedToBeAdded = $chargingAmount - $pushedTotal;
188 189 $formattedItems[] = [
189 190 'name' => __('Adjustment Amount', 'fluent-cart'),
190 191 'unit_amount' => [
191 - 'currency_code' => $transaction->currency,
192 - 'value' => number_format($extraChargeNeedToBeAdded, 2, '.', ''),
192 + 'currency_code' => $currency,
193 + 'value' => PayPalHelper::formatDecimalAmount($extraChargeNeedToBeAdded, $currency),
193 194 ],
194 195 'quantity' => 1,
195 196 ];
196 197
@@ -197,9 +198,9 @@
197 198 $purchaseUnits['items'] = $formattedItems;
198 199
199 200 //now the total amount need to be adjusted with item total value
200 201 $adjustedItemTotal = $itemsSubTotal + $extraChargeNeedToBeAdded;
201 - $purchaseUnits['amount']['breakdown']['item_total']['value'] = number_format($adjustedItemTotal, 2, '.', '');
202 + $purchaseUnits['amount']['breakdown']['item_total']['value'] = PayPalHelper::formatDecimalAmount($adjustedItemTotal, $currency);
202 203 }
203 204
204 205 // System (auto-charged, store-billed) subscription checkout: vault the
205 206 // buyer's PayPal account during this purchase (Vault v3 save-on-success)
@@ -609,8 +610,24 @@
609 610 }
610 611
611 612 $transactionUpdateData['meta'] = array_merge($transaction->meta ?? [], Arr::get($transactionArgs, 'meta', []));
612 613
614 + // A zero-decimal total is stored x100 but charged rounded, so PayPal reports back a
615 + // figure up to half a unit away from the stored one. The wire comparison upstream has
616 + // already proved this is the same payment. Keep the stored number: it is what the
617 + // order's line items sum to, so adopting the rounded one would either strand the order
618 + // partially_paid (rounded down) or fake an overpayment (rounded up). Record what
619 + // actually moved in meta instead. activateSubscription() already leaves total alone.
620 + $reportedTotal = (int)Arr::get($transactionUpdateData, 'total', 0);
621 + if ($reportedTotal
622 + && $reportedTotal !== (int)$transaction->total
623 + && PayPalHelper::currencyDecimals($transaction->currency) === 0
624 + && $reportedTotal === PayPalHelper::wireCents($transaction->total, $transaction->currency)
625 + ) {
626 + unset($transactionUpdateData['total']);
627 + $transactionUpdateData['meta']['wire_total'] = $reportedTotal;
628 + }
629 +
613 630 $transaction->fill($transactionUpdateData);
614 631 $transaction->save();
615 632
616 633 fluent_cart_add_log(__('PayPal Payment Confirmation', 'fluent-cart'), __('Payment confirmation received from PayPal. Transaction ID: ', 'fluent-cart') . Arr::get($transactionArgs, 'vendor_charge_id', ''), 'info', [
@@ -696,10 +713,12 @@
696 713 // never deliver a paid product for free.
697 714 $currencyMatches = !$lastPaymentCurrency || !$transaction->currency
698 715 || strtoupper($transaction->currency) === $lastPaymentCurrency;
699 716
717 + $expectedAmount = PayPalHelper::wireCents($transaction->total, $transaction->currency);
718 +
700 719 $initialPaymentVerified = $lastPaymentAmount
701 - && $transaction->total == $lastPaymentAmount
720 + && $expectedAmount == $lastPaymentAmount
702 721 && $currencyMatches;
703 722
704 723 if ($initialPaymentVerified || $transaction->total == 0) {
705 724 $transactionUpdateData = array_filter([
@@ -717,9 +736,9 @@
717 736 __('PayPal Subscription Payment Mismatch', 'fluent-cart'),
718 737 sprintf(
719 738 /* translators: %1$s: expected amount, %2$s: expected currency, %3$s: received amount, %4$s: received currency */
720 739 __('Subscription initial payment mismatch. Expected: %1$s %2$s, Received: %3$s %4$s. Order not marked paid; awaiting webhook.', 'fluent-cart'),
721 - Helper::toDecimal($transaction->total),
740 + Helper::toDecimal($expectedAmount),
722 741 $transaction->currency,
723 742 Helper::toDecimal($lastPaymentAmount),
724 743 $lastPaymentCurrency
725 744 ),
@@ -803,11 +822,16 @@
803 822 return $subscriptionModel;
804 823 }
805 824
806 825
807 - private function toDecimal($cents)
826 + /**
827 + * Cents to a decimal amount rounded to the precision PayPal accepts for
828 + * the currency (HUF/JPY/TWD take no decimals). Rounding before the
829 + * breakdown arithmetic keeps the parts summing to the total.
830 + */
831 + private function toDecimal($cents, $currency)
808 832 {
809 - return Helper::toDecimalWithoutComma($cents);
833 + return PayPalHelper::toDecimalAmount($cents, $currency);
810 834 }
811 835
812 836 /**
813 837 * Persist the vaulted PayPal payment token from a captured order onto the
@@ -925,9 +949,9 @@
925 949 'reference_id' => $transaction->uuid,
926 950 'custom_id' => $transaction->uuid,
927 951 'amount' => [
928 952 'currency_code' => strtoupper($transaction->currency),
929 - 'value' => number_format($this->toDecimal((int) $transaction->total), 2, '.', ''),
953 + 'value' => PayPalHelper::formatAmount((int) $transaction->total, $transaction->currency),
930 954 ],
931 955 ];
932 956
933 957 $paypalOrder = API::createOrder($purchaseUnit, [
@@ -1037,15 +1061,17 @@
1037 1061 return new \WP_Error('currency_mismatch', __('The PayPal payment currency does not match this transaction. Please verify the payment at PayPal.', 'fluent-cart'));
1038 1062 }
1039 1063
1040 1064 $captureAmount = Helper::toCent(Arr::get($capture, 'amount.value', 0));
1041 - if ($captureAmount !== (int) $transaction->total) {
1065 + $expectedAmount = PayPalHelper::wireCents($transaction->total, $transaction->currency);
1066 +
1067 + if ($captureAmount !== $expectedAmount) {
1042 1068 fluent_cart_warning_log(
1043 1069 __('PayPal Amount Mismatch On Sync', 'fluent-cart'),
1044 1070 sprintf(
1045 1071 /* translators: %1$s: expected amount, %2$s: received amount */
1046 1072 __('Capture amount mismatch detected during transaction sync. Expected: %1$s, Received: %2$s. Transaction was not confirmed.', 'fluent-cart'),
1047 - Helper::toDecimal($transaction->total),
1073 + Helper::toDecimal($expectedAmount),
1048 1074 Helper::toDecimal($captureAmount)
1049 1075 ),
1050 1076 [
1051 1077 'module_name' => 'order',