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.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 trunk All 48 releases
← All changes | app/Helpers/CheckoutProcessor.php +37 -10 1.6.2 → 1.6.5 View file →
@@ -486,19 +486,25 @@
486 486 ->where('order_id', $this->orderModel->id)
487 487 ->first();
488 488
489 489 if ($existingTransaction) {
490 + $meta = $existingTransaction->meta ?: [];
491 +
490 492 // Retry vs duplicate for gateway idempotency (PaymentInstance::getIdempotencySeed):
491 493 // re-submitting a pending transaction is a duplicate (keep attempt -> gateway
492 494 // dedupes); re-submitting a FAILED one is a retry (bump attempt -> fresh seed,
493 495 // never answered with the failed attempt's cached gateway response).
494 - $attempt = (int) Arr::get($existingTransaction->meta ?: [], 'payment_attempt', 0);
496 + $attempt = (int) Arr::get($meta, 'payment_attempt', 0);
495 497 if ($existingTransaction->status === Status::PAYMENT_FAILED) {
496 498 $attempt++;
497 499 }
500 +
501 + // The gateway object prepared last time (a Paddle transaction, a PayPal
502 + // order) is kept so the gateway can reuse it instead of creating another.
498 503 if ($attempt) {
499 - $transactionData['meta'] = ['payment_attempt' => $attempt];
504 + $meta['payment_attempt'] = $attempt;
500 505 }
506 + $transactionData['meta'] = $meta;
501 507
502 508 $existingTransaction->fill($transactionData);
503 509 $existingTransaction->save();
504 510 $this->transactionModel = $existingTransaction;
@@ -925,9 +931,9 @@
925 931 'line_meta' => Arr::get($item, 'line_meta', []),
926 932 'signup_fee' => $signupFee,
927 933 'signup_fee_tax' => $signupFeeTax,
928 934 'first_iteration_tax' => $firstIterationTax,
929 - 'is_recurring_coupon' => Arr::get($item, 'is_recurring_coupon', 'no'),
935 + 'recurring_discount' => $recurringDiscountAmount,
930 936 'total_discount' => $discountTotal
931 937 ]);
932 938
933 939 // removable upon discussion
@@ -951,13 +957,8 @@
951 957 'variation_type' => Arr::get($item, 'other_info.variation_type', '')
952 958 ]
953 959 ];
954 960
955 - // if recurring coupon is applied, we need to subtract the total discount from the recurring total
956 - if (Arr::get($item, 'is_recurring_coupon', 'no') === 'yes') {
957 - $subscriptionItem['recurring_total'] -= $discountTotal;
958 - }
959 -
960 961 $subscriptionData = wp_parse_args($subscriptionPricing, $subscriptionItem);
961 962 $paymentMethod = Arr::get($this->orderData, 'payment_method', '');
962 963
963 964 $collectionMethod = apply_filters('fluent_cart/subscription_collection_method_' . $paymentMethod, $this->determineCollectionMethod());
@@ -1103,8 +1104,30 @@
1103 1104 + $estimatedTaxTotal
1104 1105 + $estimatedShippingTax;
1105 1106
1106 1107 $orderData['total_amount'] = $totalAmount > 0 ? $totalAmount : 0;
1108 +
1109 + /**
1110 + * Filter the prepared order data before it is used for order creation.
1111 + *
1112 + * This runs after FluentCart calculates totals, so plugins can adjust
1113 + * currency, rate, totals, config, mode, or any other order field before
1114 + * the order model, transaction, and subscription are derived from it.
1115 + *
1116 + * @param array $orderData Prepared order data array.
1117 + * @param array $context {
1118 + * Additional context for the filter.
1119 + *
1120 + * @type array $items Formatted order items with prices and quantities.
1121 + * @type array $args Checkout arguments: customer data, payment method,
1122 + * shipping, tax, coupons, fees, and IP data.
1123 + * }
1124 + */
1125 + $orderData = apply_filters('fluent_cart/checkout/order_data', $orderData, [
1126 + 'items' => $this->formattedIOrderItems,
1127 + 'args' => $this->args,
1128 + ]);
1129 +
1107 1130 $this->orderData = $orderData;
1108 1131 }
1109 1132
1110 1133 private function syncFeeItems()
@@ -1229,8 +1252,9 @@
1229 1252 $signupFee = (int)($inputData['signup_fee'] ?? 0);
1230 1253 $signupFeeTax = (int)($inputData['signup_fee_tax'] ?? 0);
1231 1254 $firstIterationTax = (int)($inputData['first_iteration_tax'] ?? 0);
1232 1255 $totalDiscount = (int)($inputData['total_discount'] ?? 0);
1256 + $recurringDiscount = (int)($inputData['recurring_discount'] ?? 0);
1233 1257
1234 1258 // Determine if THIS subscription item is tax-inclusive (for behavior=3 mixed carts)
1235 1259 $taxBehavior = (int) Arr::get($inputData, 'tax_behavior', 0);
1236 1260 $itemInclusive = (bool) Arr::get($inputData, 'line_meta.tax_config.inclusive', false);
@@ -1270,10 +1294,13 @@
1270 1294 }
1271 1295 } else {
1272 1296 $firstCycleCost = $recurringAmount + $signupFee - $totalDiscount;
1273 1297
1274 - if (Arr::get($inputData, 'is_recurring_coupon', 'no') === 'yes') {
1275 - $recurringAmount -= $totalDiscount; // as now discount applied on recurring amount
1298 + // A recurring coupon discounts every cycle, so the per-cycle price itself
1299 + // is lower — the first cycle is not cheaper than the ones after it and
1300 + // must not be expressed as a trial.
1301 + if ($recurringDiscount > 0) {
1302 + $recurringAmount -= $recurringDiscount;
1276 1303 }
1277 1304
1278 1305 if ($firstCycleCost < $recurringAmount) {
1279 1306 $adjustedTrialDays = Helper::calculateAdjustedTrialDaysForInterval($trialDays, $repeatInterval);