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/Modules/Subscriptions/Http/Controllers/SubscriptionController.php +101 -2 1.6.0 → 1.6.5 View file →
@@ -1,16 +1,20 @@
1 1 <?php
2 2
3 3 namespace FluentCart\App\Modules\Subscriptions\Http\Controllers;
4 4
5 +use FluentCart\App\App;
5 6 use FluentCart\App\Helpers\Status;
6 7 use FluentCart\App\Http\Controllers\Controller;
7 8 use FluentCart\App\Models\Order;
8 9 use FluentCart\App\Models\Subscription;
10 +use FluentCart\App\Modules\PaymentMethods\Core\AbstractPaymentGateway;
9 11 use FluentCart\App\Modules\Subscriptions\Services\EarlyPaymentFeature;
10 12 use FluentCart\App\Services\Reminders\ReminderService;
11 13 use FluentCart\App\Http\Requests\UpdateSubscriptionRequest;
14 +use FluentCart\App\Http\Requests\UpdateVendorIdsRequest;
12 15 use FluentCart\Framework\Http\Request\Request;
16 +use FluentCart\Framework\Support\Arr;
13 17 use FluentCart\App\Modules\StoreManagedRenewal\Services\RenewalService;
14 18 use FluentCart\App\Modules\Subscriptions\Services\SubscriptionService;
15 19 use FluentCart\App\Modules\Subscriptions\Services\Filter\SubscriptionFilter;
16 20
@@ -30,8 +34,12 @@
30 34
31 35 $subscription = Subscription::with([
32 36 'labels',
33 37 'activities.user',
38 + // Served by the fct_order_transactions subscription_id index
39 + // (OrderTransactionsMigrator base schema + migrated(), delivered to
40 + // existing stores via DBMigrator::maybeMigrateDBChanges at 1.0.49).
41 + 'transactions',
34 42 'customer.shipping_address' => function ($query) {
35 43 $query->where('is_primary', 1);
36 44 },
37 45 'customer.billing_address' => function ($query) {
@@ -54,8 +62,14 @@
54 62 // Attach parent order's addresses and business info directly to subscription for consistent access
55 63 $subscription->billing_address = $subscription->order->billing_address ?? null;
56 64 $subscription->shipping_address = $subscription->order->shipping_address ?? null;
57 65 $subscription->business_info = $subscription->order ? $subscription->order->getBusinessInfo() : [];
66 + // Upgrade eligibility, same flag the customer portal exposes
67 + // (CustomerSubscriptionController) — gateway-free: an upgrade-path meta
68 + // existence check plus a status check.
69 + if ($subscription instanceof Subscription) {
70 + $subscription->can_upgrade = $subscription->canUpgrade();
71 + }
58 72
59 73 $subscription->related_orders = Order::query()
60 74 ->with(['order_items' => function ($query) {
61 75 $query->select('id', 'order_id', 'post_title', 'title', 'quantity', 'payment_type', 'line_meta');
@@ -86,11 +100,13 @@
86 100 }
87 101
88 102 private function validateOrderBinding(Order $order, Subscription $subscription)
89 103 {
90 - if ((int) $subscription->parent_order_id !== (int) $order->id) {
104 + $rootOrderId = $order->parent_id ? $order->parent_id : $order->id;
105 + if ((int) $subscription->parent_order_id !== (int) $rootOrderId) {
91 106 return $this->sendError(['message' => __('Subscription does not belong to this order.', 'fluent-cart')], 403);
92 107 }
108 +
93 109 return null;
94 110 }
95 111
96 112 public function cancelSubscription(Request $request, Order $order, Subscription $subscription)
@@ -148,8 +164,12 @@
148 164 'status' => Status::SUBSCRIPTION_CANCELED,
149 165 'canceled_at' => gmdate('Y-m-d H:i:s'),
150 166 ])->save();
151 167
168 + // Same record the vendor branch keeps via cancelRemoteSubscription — merged
169 + // after save() so the write is not undone by the fill above.
170 + $subscription->mergeConfig(['cancellation_reason' => $reason]);
171 +
152 172 // Single cancel chokepoint — void renewals, clear reminders, email once.
153 173 SubscriptionService::finalizeCancellation($subscription, $reason ?: 'Subscription canceled by admin');
154 174 }
155 175
@@ -273,8 +293,83 @@
273 293 'subscription' => Subscription::query()->find($subscription->id)
274 294 ]);
275 295 }
276 296
297 + public function updateVendorIds(UpdateVendorIdsRequest $request, Order $order, Subscription $subscription)
298 + {
299 + $this->validateSubscription($subscription);
300 + if ($error = $this->validateOrderBinding($order, $subscription)) return $error;
301 +
302 + $data = $request->all();
303 + $payload = [];
304 +
305 + // Only the keys actually sent are written — an absent key must not blank
306 + // the stored id.
307 + foreach (['vendor_subscription_id', 'vendor_customer_id'] as $field) {
308 + if (array_key_exists($field, $data)) {
309 + $payload[$field] = (string) $data[$field];
310 + }
311 + }
312 +
313 + if (empty($payload)) {
314 + return $this->sendError([
315 + 'message' => __('No data provided for update.', 'fluent-cart')
316 + ]);
317 + }
318 +
319 + $result = SubscriptionService::updateVendorIds($subscription, $payload);
320 +
321 + if (is_wp_error($result)) {
322 + return $this->sendError([
323 + 'message' => $result->get_error_message()
324 + ]);
325 + }
326 +
327 + return $this->sendSuccess([
328 + 'message' => __('Vendor IDs have been updated successfully!', 'fluent-cart'),
329 + 'subscription' => Subscription::query()->find($subscription->id)
330 + ]);
331 + }
332 +
333 + public function verifyVendorIds(UpdateVendorIdsRequest $request, Order $order, Subscription $subscription)
334 + {
335 + $this->validateSubscription($subscription);
336 + if ($error = $this->validateOrderBinding($order, $subscription)) return $error;
337 +
338 + if (!$subscription->canEditVendorIds()) {
339 + return $this->sendError([
340 + 'message' => __('Vendor IDs can only be edited on an active gateway-billed subscription.', 'fluent-cart')
341 + ]);
342 + }
343 +
344 + if (!$subscription->canVerifyVendorIds()) {
345 + return $this->sendError([
346 + 'message' => __('This payment method does not support subscription lookup.', 'fluent-cart')
347 + ]);
348 + }
349 +
350 + /** @var AbstractPaymentGateway $gateway */
351 + $gateway = App::gateway($subscription->current_payment_method);
352 +
353 + $data = $request->all();
354 +
355 + $result = $gateway->subscriptions->verifyVendorSubscription([
356 + 'vendor_subscription_id' => (string) Arr::get($data, 'vendor_subscription_id', ''),
357 + 'vendor_customer_id' => (string) Arr::get($data, 'vendor_customer_id', ''),
358 + ], $order->mode);
359 +
360 + if (is_wp_error($result)) {
361 + return $this->sendError([
362 + 'message' => $result->get_error_message()
363 + ]);
364 + }
365 +
366 + return $this->sendSuccess([
367 + 'message' => __('Subscription found at the payment gateway.', 'fluent-cart'),
368 + 'verification' => $result
369 + ]);
370 + }
371 +
277 372 public function generateEarlyPaymentLink(Request $request, Order $order, Subscription $subscription)
278 373 {
279 374 $this->validateSubscription($subscription);
280 375
@@ -284,9 +379,13 @@
284 379 ]);
285 380 }
286 381
287 382 $subscriptionOrderId = null;
288 - if (property_exists($subscription, 'parent_order_id') && $subscription->parent_order_id) {
383 + // property_exists() cannot see Eloquent attributes (they live in the
384 + // model's attributes array behind __get), so it returned false even for
385 + // populated rows and every request died on the mismatch guard below.
386 + // isset() routes through __isset and sees the real attribute.
387 + if (isset($subscription->parent_order_id) && $subscription->parent_order_id) {
289 388 $subscriptionOrderId = (int) $subscription->parent_order_id;
290 389 }
291 390
292 391 if ($subscriptionOrderId === null || $subscriptionOrderId !== (int) $order->id) {