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 +47 -10 1.6.1 → 1.6.5 View file →
@@ -246,8 +246,18 @@
246 246 $cart->user_id = $customer->user_id;
247 247 }
248 248
249 249 $cart->save();
250 +
251 + // Carry the traffic source onto the order while the cart still exists.
252 + // Carts are pruned on a schedule, so this is the last reliable point at
253 + // which the click that produced the sale can still be recovered.
254 + UtmHelper::addUtmToOrder(
255 + $this->orderModel->id,
256 + UtmHelper::resolveUtmData(UtmHelper::getUtmDataOfRequest(), $cart->utm_data),
257 + $cart->cart_hash
258 + );
259 +
250 260 $actions = Arr::get($cart->checkout_data, '__after_draft_created_actions__', []);
251 261 if ($actions) {
252 262 foreach ($actions as $actionName) {
253 263 $actionName = (string)$actionName;
@@ -476,19 +486,25 @@
476 486 ->where('order_id', $this->orderModel->id)
477 487 ->first();
478 488
479 489 if ($existingTransaction) {
490 + $meta = $existingTransaction->meta ?: [];
491 +
480 492 // Retry vs duplicate for gateway idempotency (PaymentInstance::getIdempotencySeed):
481 493 // re-submitting a pending transaction is a duplicate (keep attempt -> gateway
482 494 // dedupes); re-submitting a FAILED one is a retry (bump attempt -> fresh seed,
483 495 // never answered with the failed attempt's cached gateway response).
484 - $attempt = (int) Arr::get($existingTransaction->meta ?: [], 'payment_attempt', 0);
496 + $attempt = (int) Arr::get($meta, 'payment_attempt', 0);
485 497 if ($existingTransaction->status === Status::PAYMENT_FAILED) {
486 498 $attempt++;
487 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.
488 503 if ($attempt) {
489 - $transactionData['meta'] = ['payment_attempt' => $attempt];
504 + $meta['payment_attempt'] = $attempt;
490 505 }
506 + $transactionData['meta'] = $meta;
491 507
492 508 $existingTransaction->fill($transactionData);
493 509 $existingTransaction->save();
494 510 $this->transactionModel = $existingTransaction;
@@ -915,9 +931,9 @@
915 931 'line_meta' => Arr::get($item, 'line_meta', []),
916 932 'signup_fee' => $signupFee,
917 933 'signup_fee_tax' => $signupFeeTax,
918 934 'first_iteration_tax' => $firstIterationTax,
919 - 'is_recurring_coupon' => Arr::get($item, 'is_recurring_coupon', 'no'),
935 + 'recurring_discount' => $recurringDiscountAmount,
920 936 'total_discount' => $discountTotal
921 937 ]);
922 938
923 939 // removable upon discussion
@@ -941,13 +957,8 @@
941 957 'variation_type' => Arr::get($item, 'other_info.variation_type', '')
942 958 ]
943 959 ];
944 960
945 - // if recurring coupon is applied, we need to subtract the total discount from the recurring total
946 - if (Arr::get($item, 'is_recurring_coupon', 'no') === 'yes') {
947 - $subscriptionItem['recurring_total'] -= $discountTotal;
948 - }
949 -
950 961 $subscriptionData = wp_parse_args($subscriptionPricing, $subscriptionItem);
951 962 $paymentMethod = Arr::get($this->orderData, 'payment_method', '');
952 963
953 964 $collectionMethod = apply_filters('fluent_cart/subscription_collection_method_' . $paymentMethod, $this->determineCollectionMethod());
@@ -1093,8 +1104,30 @@
1093 1104 + $estimatedTaxTotal
1094 1105 + $estimatedShippingTax;
1095 1106
1096 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 +
1097 1130 $this->orderData = $orderData;
1098 1131 }
1099 1132
1100 1133 private function syncFeeItems()
@@ -1219,8 +1252,9 @@
1219 1252 $signupFee = (int)($inputData['signup_fee'] ?? 0);
1220 1253 $signupFeeTax = (int)($inputData['signup_fee_tax'] ?? 0);
1221 1254 $firstIterationTax = (int)($inputData['first_iteration_tax'] ?? 0);
1222 1255 $totalDiscount = (int)($inputData['total_discount'] ?? 0);
1256 + $recurringDiscount = (int)($inputData['recurring_discount'] ?? 0);
1223 1257
1224 1258 // Determine if THIS subscription item is tax-inclusive (for behavior=3 mixed carts)
1225 1259 $taxBehavior = (int) Arr::get($inputData, 'tax_behavior', 0);
1226 1260 $itemInclusive = (bool) Arr::get($inputData, 'line_meta.tax_config.inclusive', false);
@@ -1260,10 +1294,13 @@
1260 1294 }
1261 1295 } else {
1262 1296 $firstCycleCost = $recurringAmount + $signupFee - $totalDiscount;
1263 1297
1264 - if (Arr::get($inputData, 'is_recurring_coupon', 'no') === 'yes') {
1265 - $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;
1266 1303 }
1267 1304
1268 1305 if ($firstCycleCost < $recurringAmount) {
1269 1306 $adjustedTrialDays = Helper::calculateAdjustedTrialDaysForInterval($trialDays, $repeatInterval);