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/AdminOrderProcessor.php +55 -2 1.5.2 → 1.6.5 View file →
@@ -275,13 +275,35 @@
275 275 'note' => Arr::get($this->args, 'note', ''),
276 276 'ip_address' => Arr::get($this->args, 'ip_address', ''),
277 277 'config' => [
278 278 'user_tz' => Arr::get($this->args, 'user_tz', ''),
279 + 'source' => 'admin',
279 280 ],
280 281 ];
281 282
282 283 $totalAmount = $orderData['subtotal'] - $orderData['coupon_discount_total'] - $orderData['manual_discount_total'] + $orderData['shipping_total'] + $orderData['tax_total'];
283 284 $orderData['total_amount'] = $totalAmount > 0 ? $totalAmount : 0;
285 +
286 + /**
287 + * Filter the prepared order data before it is used for order creation.
288 + *
289 + * This mirrors the frontend checkout hook so integrations can adjust
290 + * order payload fields consistently for manual admin orders too.
291 + *
292 + * @param array $orderData Prepared order data array.
293 + * @param array $context {
294 + * Additional context for the filter.
295 + *
296 + * @type array $items Formatted order items with prices and quantities.
297 + * @type array $args Admin order arguments: customer data, payment
298 + * method, shipping, tax, notes, and IP data.
299 + * }
300 + */
301 + $orderData = apply_filters('fluent_cart/checkout/order_data', $orderData, [
302 + 'items' => $this->formattedIOrderItems,
303 + 'args' => $this->args,
304 + ]);
305 +
284 306 $this->orderData = $orderData;
285 307 }
286 308
287 309
@@ -386,8 +408,37 @@
386 408 $subscriptionData = $this->subscriptionData;
387 409 $subscriptionData['customer_id'] = $customerId;
388 410 $subscriptionData['parent_order_id'] = $this->orderModel->id;
389 411 $this->subscriptionModel = Subscription::query()->create($subscriptionData);
412 +
413 + // bill_count counting can't tell "billed cycle" from "something else
414 + // charged alongside it." Must be decided at creation: payment-method switching
415 + // also sets is_trial_days_simulated, so the flag alone can't be trusted at runtime.
416 + $isSimulated = Arr::get($subscriptionData, 'config.is_trial_days_simulated', 'no') === 'yes';
417 + $trialDays = (int)Arr::get($subscriptionData, 'trial_days', 0);
418 + $billTimes = (int)$this->subscriptionModel->bill_times;
419 + $orderTotal = (int)$this->orderModel->total_amount;
420 +
421 + // Simulated trial, $0 first cycle: consumes a cycle but produces no
422 + // total > 0 transaction — add billed_cycles_offset so it still counts.
423 + if ($isSimulated
424 + && $billTimes > 0
425 + && (int)$this->subscriptionModel->signup_fee <= 0
426 + && !$orderTotal
427 + ) {
428 + $this->subscriptionModel->updateMeta('billed_cycles_offset', 1);
429 + }
430 +
431 + // Real trial with a signup fee: the initial charge is the signup fee only,
432 + // but it IS a total > 0 transaction linked to the subscription — mark
433 + // billed_cycles_deduction so it does NOT count as a cycle.
434 + if (!$isSimulated
435 + && $trialDays > 0
436 + && $billTimes > 0
437 + && $orderTotal > 0
438 + ) {
439 + $this->subscriptionModel->updateMeta('billed_cycles_deduction', 1);
440 + }
390 441 }
391 442
392 443 // Let's create the transaction
393 444 $transactionData = [
@@ -505,9 +556,9 @@
505 556 if ($firstPrice < $recurringPrice) {
506 557 Arr::set($item, 'other_info.trial_days', PaymentHelper::getIntervalDays(Arr::get($item, 'other_info.repeat_interval')));
507 558 Arr::set($item, 'other_info.signup_fee', $firstPrice);
508 559 Arr::set($item, 'other_info.manage_setup_fee', 'yes');
509 - Arr::set($item, 'other_info.times', Arr::get($item, 'other_info.times', 0) > 1 ? Arr::get($item, 'other_info.times', 0) - 1 : 0);
560 + // times stays the full installment count — the simulated trial cycle is installment #1
510 561 } else if ($firstPrice > $recurringPrice) {
511 562 Arr::set($item, 'other_info.signup_fee', $firstPrice - $recurringPrice);
512 563 Arr::set($item, 'other_info.manage_setup_fee', 'yes');
513 564 Arr::set($item, 'other_info.trial_days', 0);
@@ -533,8 +584,9 @@
533 584 'bill_count' => 0,
534 585 'quantity' => 1,
535 586 'variation_id' => Arr::get($item, 'object_id', 0),
536 587 'status' => Status::SUBSCRIPTION_PENDING,
588 + 'collection_method' => Status::SUBSCRIPTION_METHOD_MANUAL,
537 589 'config' => [
538 590 'currency' => $this->orderData['currency'],
539 591 'is_trial_days_simulated' => Arr::get($subscriptionPricing, 'is_trial_days_simulated', 'no'),
540 592 // Snapshot the variant attribute map + variation type from the order
@@ -673,9 +725,10 @@
673 725 $result['trial_days'] = $adjustedTrialDays;
674 726 $result['is_trial_days_simulated'] = 'yes';
675 727 $result['signup_fee'] = $firstCycleCost;
676 728 $result['manage_setup_fee'] = 'yes';
677 - $result['times'] = $times > 0 ? $times - 1 : 0;
729 + // bill_times stays the full installment count. The simulated trial cycle IS the
730 + // first installment; gateways derive remaining remote cycles from is_trial_days_simulated.
678 731 } else if ($firstCycleCost > $recurringAmount) {
679 732 $result['trial_days'] = 0;
680 733 $result['signup_fee'] = $firstCycleCost - $recurringAmount;
681 734 $result['manage_setup_fee'] = 'yes';