PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
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 1.2.0 All 47 releases
fluent-cart / app / Http / Controllers / RenewalController.php

RenewalController.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at app/Http/Controllers/RenewalController.php

184 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Http\Controllers;
4
5 use FluentCart\App\Helpers\Status;
6 use FluentCart\App\Models\Order;
7 use FluentCart\App\Models\OrderTransaction;
8 use FluentCart\App\Modules\StoreManagedRenewal\Services\RenewalService;
9 use FluentCart\App\Modules\Subscriptions\Services\SystemChargeService;
10 use FluentCart\Framework\Http\Request\Request;
11
12 class RenewalController extends Controller
13 {
14 /**
15 * List all invoices (renewal orders)
16 */
17 public function index(Request $request): \WP_REST_Response
18 {
19 $query = Order::query()
20 ->where('type', Status::ORDER_TYPE_RENEWAL)
21 ->with(['customer']);
22
23 if ($paymentStatus = $request->get('payment_status')) {
24 $query->where('payment_status', sanitize_text_field($paymentStatus));
25 }
26
27 if ($parentId = $request->get('parent_id')) {
28 $query->where('parent_id', (int) $parentId);
29 }
30
31 if ($customerId = $request->get('customer_id')) {
32 $query->where('customer_id', (int) $customerId);
33 }
34
35 $invoices = $query->orderBy('id', 'DESC')->paginate();
36
37 return $this->response->sendSuccess([
38 'invoices' => $invoices
39 ]);
40 }
41
42 /**
43 * Get single invoice details
44 */
45 public function show(Request $request, int $id): \WP_REST_Response
46 {
47 $invoice = Order::query()
48 ->where('type', Status::ORDER_TYPE_RENEWAL)
49 ->with(['customer', 'order_items', 'transactions'])
50 ->find($id);
51
52 if (!$invoice) {
53 return $this->entityNotFoundError(
54 __('Renewal order not found', 'fluent-cart'),
55 __('Back to Orders', 'fluent-cart'),
56 '/orders'
57 );
58 }
59
60 return $this->response->sendSuccess([
61 'invoice' => $invoice
62 ]);
63 }
64
65 /**
66 * Void/cancel a pending invoice
67 */
68 public function void(Request $request, Order $order): \WP_REST_Response
69 {
70 if ($order->type !== Status::ORDER_TYPE_RENEWAL) {
71 return $this->sendError([
72 'message' => __('Only renewal orders can be voided', 'fluent-cart')
73 ], 400);
74 }
75
76 if (!in_array($order->payment_status, [Status::PAYMENT_PENDING, Status::PAYMENT_SCHEDULED])) {
77 return $this->sendError([
78 'message' => __('Only pending or scheduled invoices can be voided', 'fluent-cart')
79 ], 400);
80 }
81
82 // Re-assert payment_status at mutation time — a webhook may pay this invoice
83 // between the check above and this update. A raced-paid invoice must not be
84 // flipped to canceled/failed.
85 $voided = Order::query()
86 ->where('id', $order->id)
87 ->whereIn('payment_status', [Status::PAYMENT_PENDING, Status::PAYMENT_SCHEDULED])
88 ->update([
89 'payment_status' => Status::PAYMENT_FAILED,
90 'status' => Status::ORDER_CANCELED,
91 ]);
92
93 if (!$voided) {
94 return $this->sendError([
95 'message' => __('This invoice was just paid and can no longer be voided', 'fluent-cart')
96 ], 409);
97 }
98
99 $order->payment_status = Status::PAYMENT_FAILED;
100 $order->status = Status::ORDER_CANCELED;
101
102 // Cancel associated pending transactions
103 OrderTransaction::query()
104 ->where('order_id', $order->id)
105 ->where('status', Status::TRANSACTION_PENDING)
106 ->update(['status' => Status::TRANSACTION_FAILED]);
107
108 $order->addLog(
109 'Renewal order voided',
110 'Renewal order manually voided by admin',
111 'warning'
112 );
113
114 // Advance the subscription past this period so the scheduler doesn't
115 // immediately regenerate the same invoice.
116 RenewalService::advanceAfterVoid($order);
117
118 if ($order->customer) {
119 $order->customer->recountStat();
120 }
121
122 do_action('fluent_cart/order_canceled', [
123 'order' => $order,
124 'customer' => $order->customer,
125 ]);
126
127 do_action('fluent_cart/renewal_voided', [
128 'order' => $order,
129 'customer' => $order->customer,
130 ]);
131
132 return $this->response->sendSuccess([
133 'message' => __('Renewal order has been voided successfully', 'fluent-cart')
134 ]);
135 }
136
137 /**
138 * Resend invoice email notification
139 */
140 public function resend(Request $request, Order $order): \WP_REST_Response
141 {
142 if ($order->type !== Status::ORDER_TYPE_RENEWAL) {
143 return $this->sendError([
144 'message' => __('Only renewal orders can be resent', 'fluent-cart')
145 ], 400);
146 }
147
148 if ($order->payment_status !== Status::PAYMENT_PENDING) {
149 return $this->sendError([
150 'message' => __('Only pending renewal orders can be resent', 'fluent-cart')
151 ], 400);
152 }
153
154 $subscription = $order->parentOrder ? $order->parentOrder->subscriptions()->first() : null;
155
156 // A system invoice flips to PENDING on its first failed auto-charge attempt
157 // while a retry is still armed — resend must wait for the ladder to exhaust
158 // instead of firing the pay-now email mid-dunning.
159 if ($subscription && $subscription->isSystem() && !SystemChargeService::isExhausted($subscription, $order)) {
160 return $this->sendError([
161 'message' => __('This invoice is still being retried automatically. Resend is available once retries are exhausted.', 'fluent-cart')
162 ], 400);
163 }
164
165 do_action('fluent_cart/renewal_created', [
166 'subscription' => $subscription,
167 'order' => $order,
168 'parent_order' => $order->parentOrder,
169 'customer' => $order->customer,
170 'transaction' => $order->transactions()->first(),
171 ]);
172
173 $order->addLog(
174 'Renewal order email resent',
175 'Renewal order email notification resent by admin',
176 'info'
177 );
178
179 return $this->response->sendSuccess([
180 'message' => __('Renewal order email has been resent', 'fluent-cart')
181 ]);
182 }
183 }
184