PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.5
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.5
2.4.5 2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 All 69 releases
← All changes | app/Services/Integrations/FluentCart/FluentCart.php +279 -200 1.10.1 → 2.4.5 View file →
@@ -1,292 +1,372 @@
1 1 <?php
2 2
3 3 namespace FluentSupport\App\Services\Integrations\FluentCart;
4 +use FluentCart\App\Helpers\Helper as CartHelper;
5 +use FluentCart\App\Models\Customer as CartCustomer;
4 6 use FluentCart\App\Models\Order;
7 +use FluentCart\App\Services\URL;
5 8 use FluentSupport\App\Services\Helper;
6 -use FluentSupport\Framework\Support\Arr;
7 -use FluentSupport\App\Models\Customer;
9 +use FluentSupport\App\Services\ProfileInfoService;
8 10
9 11 class FluentCart
10 12 {
13 + const WIDGET_KEY = 'fluent_cart_purchases';
14 +
11 15 public function boot()
12 16 {
13 -// add_filter('fluent_support/customer_extra_widgets', array($this, 'getFluentCartPurchaseWidgets'), 120, 2);
14 - // add_filter('fluent_support/customer_extra_widgets', array($this, 'getFluentCartProLicenseWidget'), 125, 2);
15 - add_action('fluent_cart/order_created', [$this, 'addCustomer'], 10, 1);
16 - if (!apply_filters('fluent_support/disable_fc_menu', false)) {
17 + add_filter('fluent_support/customer_extra_widgets', array($this, 'getPurchaseWidgets'), 120, 3);
18 +
19 + if (
20 + !apply_filters('fluent_support/disable_fc_menu', false) &&
21 + Helper::getBusinessSettings('ticket_link_portal') === 'fluent_cart'
22 + ) {
17 23 $this->renderCustomerPortalInFluentCartDashboard();
18 24 }
19 25 }
20 26
21 - public function getFluentCartPurchaseWidgets($widgets, $customer)
27 + public function getPurchaseWidgets($widgets, $customer, $context = null)
22 28 {
23 - $wpUserId = $customer->user_id;
29 + if (!ProfileInfoService::wantsWidget($context, self::WIDGET_KEY)) {
30 + return $widgets;
31 + }
24 32
25 - // Get all order items for this customer, grouped by product
26 - $orderItems = \FluentCart\App\Models\OrderItem::whereHas('order.customer', function ($query) use ($wpUserId) {
27 - $query->where('user_id', $wpUserId);
28 - })
29 - ->with([
30 - 'order' => function ($query) {
31 - $query->select(['id', 'status', 'created_at', 'total_amount', 'currency', 'invoice_no']);
32 - }
33 - ])
34 - ->select(['id', 'order_id', 'post_id', 'post_title', 'title', 'payment_type', 'unit_price', 'subtotal', 'quantity'])
35 - ->orderByDesc('id')
36 - ->get();
33 + $cartCustomer = $this->findCustomer($customer);
37 34
38 - if ($orderItems->isEmpty()) {
35 + if (!$cartCustomer) {
39 36 return $widgets;
40 37 }
41 38
42 - // Create individual product entries for each order item (no grouping)
43 - $formattedProducts = [];
39 + // The totals come from the customer record; the list needs only the latest orders.
40 + $orders = Order::query()->where('customer_id', $cartCustomer->id)
41 + ->orderBy('id', 'DESC')
42 + ->with(['order_items', 'subscriptions'])
43 + ->limit(50)
44 + ->get();
44 45
45 - foreach ($orderItems as $item) {
46 - $licenseType = $this->getLicenseType($item);
46 + if ($orders->isEmpty()) {
47 + return $widgets;
48 + }
47 49
48 - $formattedProducts[] = [
49 - 'product_name' => $item->post_title,
50 - 'variation_name' => $item->title !== $item->post_title ? $item->title : null,
51 - 'license_type' => $licenseType,
52 - 'price' => $item->unit_price,
53 - 'currency' => $item->order->currency,
54 - 'formatted_price' => $item->formatted_total,
55 - 'status' => $this->getProductStatus($item),
56 - 'order' => [
57 - 'id' => $item->order->id,
58 - 'invoice_no' => $item->order->invoice_no,
59 - 'status' => $item->order->status,
60 - 'date' => $item->order->created_at->format('Y-m-d H:i:s'),
61 - 'currency' => $item->order->currency,
62 - 'total' => number_format($item->order->total_amount / 100, 2, '.', ''),
63 - 'quantity' => $item->quantity
64 - ]
50 + if (ProfileInfoService::isMcpWidgetRequest($context)) {
51 + $widgets[self::WIDGET_KEY] = [
52 + 'title' => __('FluentCart Purchases', 'fluent-support'),
53 + 'mcp' => $this->getMcpData($orders)
65 54 ];
66 - }
67 55
68 - // Sort products by most recent order date
69 - usort($formattedProducts, function ($a, $b) {
70 - return strtotime($b['order']['date']) - strtotime($a['order']['date']);
71 - });
72 -
73 - // Get FluentCart customer ID
74 - $fluentCartCustomer = \FluentCart\App\Models\Customer::where('user_id', $wpUserId)->first();
75 - if (!$fluentCartCustomer) {
76 56 return $widgets;
77 57 }
78 58
79 - $widgets['fct_purchases'] = [
80 - 'title' => __('Fluent Cart Purchases', 'fluent-support'),
81 - 'products' => $formattedProducts,
82 - 'fluent_cart_customer_id' => $fluentCartCustomer->id,
83 - 'summary' => [
84 - 'lifetime_value' => \FluentCart\App\Helpers\Helper::toDecimal($fluentCartCustomer->ltv),
85 - 'currency' => Arr::get($formattedProducts, '0.currency', \FluentCart\Api\CurrencySettings::get('currency')),
86 - 'total_purchases' => $fluentCartCustomer->purchase_count,
87 - 'first_purchase' => $fluentCartCustomer->first_purchase_date ? date('F j, Y', strtotime($fluentCartCustomer->first_purchase_date)) : null,
88 - 'last_purchase' => $fluentCartCustomer->last_purchase_date ? date('F j, Y', strtotime($fluentCartCustomer->last_purchase_date)) : null,
89 - ]
90 - ];
59 + $widgets[self::WIDGET_KEY] = array_merge(
60 + ['title' => __('FluentCart Purchases', 'fluent-support')],
61 + $this->getWidgetData($cartCustomer, $orders)
62 + );
91 63
92 64 return $widgets;
93 65 }
94 66
95 - // public function getFluentCartProLicenseWidget($widgets, $customer)
96 - // {
97 - // // Add customer's product licenses if available
98 - // $licenses = $this->getCustomerProductLicenses($customer);
67 + /**
68 + * Stats and recent purchases, drawn by the support site's sidebar as tiles and a list.
69 + *
70 + * @param CartCustomer $cartCustomer
71 + * @param \FluentCart\Framework\Support\Collection $orders Newest first.
72 + * @return array
73 + */
74 + private function getWidgetData($cartCustomer, $orders)
75 + {
76 + $stats = [
77 + [
78 + 'label' => __('Lifetime Value', 'fluent-support'),
79 + 'value' => self::formatAmount($cartCustomer->ltv, $orders->first()->currency),
80 + 'url' => URL::getDashboardUrl('customers/' . $cartCustomer->id . '/view'),
81 + ],
82 + [
83 + 'label' => __('Paid Purchases', 'fluent-support'),
84 + 'value' => (string)(int)$cartCustomer->purchase_count,
85 + ],
86 + ];
99 87
100 - // if ($licenses) {
101 - // $widgets['fct_license'] = [
102 - // 'header' => __('Fluent Cart Product Licenses', 'fluent-support'),
103 - // 'licenses' => $licenses
104 - // ];
105 - // }
88 + if ($cartCustomer->first_purchase_date) {
89 + $stats[] = [
90 + 'label' => __('First Paid Order', 'fluent-support'),
91 + 'value' => self::gmtDateIso($cartCustomer->first_purchase_date),
92 + 'type' => 'date',
93 + ];
94 + }
106 95
107 - // return $widgets;
108 - // }
96 + if ($cartCustomer->last_purchase_date) {
97 + $stats[] = [
98 + 'label' => __('Last Paid Order', 'fluent-support'),
99 + 'value' => self::gmtDateIso($cartCustomer->last_purchase_date),
100 + 'type' => 'date',
101 + ];
102 + }
109 103
110 - public function addCustomer($param)
111 - {
112 - $fluentCartCustomer = Arr::get($param, 'customer');
104 + $orderIds = $orders->pluck('id')->toArray();
113 105
114 - if (empty($fluentCartCustomer['email'])) {
115 - return;
106 + // Paid renewals repeat their subscription's first order every term: fold them into it.
107 + // Unpaid ones (pending, scheduled, failed) stay as their own rows with a badge.
108 + $renewals = [];
109 + foreach ($orders as $order) {
110 + $isPaid = in_array($order->payment_status, ['paid', 'partially_refunded'], true);
111 +
112 + if ($order->type === 'renewal' && $isPaid && in_array($order->parent_id, $orderIds)) {
113 + if (!isset($renewals[$order->parent_id])) {
114 + // Orders are newest first, so the first renewal seen is the latest.
115 + $renewals[$order->parent_id] = ['count' => 0, 'latest' => $order];
116 + }
117 + $renewals[$order->parent_id]['count']++;
118 + }
116 119 }
117 120
118 - $customerData = [
119 - 'email' => $fluentCartCustomer['email'],
120 - 'first_name' => $fluentCartCustomer['first_name'] ?? '',
121 - 'last_name' => $fluentCartCustomer['last_name'] ?? '',
122 - 'status' => 'active'
123 - ];
121 + $items = [];
122 + foreach ($orders as $order) {
123 + if ($order->type === 'renewal' && isset($renewals[$order->parent_id]) && in_array($order->payment_status, ['paid', 'partially_refunded'], true)) {
124 + continue;
125 + }
124 126
125 - // Add user_id if available
126 - if (!empty($fluentCartCustomer['user_id'])) {
127 - $customerData['user_id'] = $fluentCartCustomer['user_id'];
128 - }
127 + $subscription = $order->subscriptions->first();
128 + $renewal = isset($renewals[$order->id]) ? $renewals[$order->id] : null;
129 + $lastPaidAt = $renewal ? $renewal['latest']->created_at : $order->created_at;
129 130
130 - // Address field mappings for efficient processing
131 - $addressMappings = [
132 - 'city' => 'city',
133 - 'state' => 'state',
134 - 'country' => 'country',
135 - 'postcode' => 'zip'
136 - ];
131 + if ($subscription) {
132 + $badge = [
133 + 'text' => (string)$subscription->status,
134 + 'type' => $subscription->status === 'active' ? 'success' : 'warning',
135 + ];
136 + } elseif ($order->total_refund) {
137 + $badge = ['text' => __('Refunded', 'fluent-support'), 'type' => 'danger'];
138 + } elseif ($order->payment_status !== 'paid') {
139 + $badge = ['text' => (string)$order->payment_status, 'type' => 'warning'];
140 + } else {
141 + $badge = null;
142 + }
137 143
138 - // Process primary address fields
139 - foreach ($addressMappings as $source => $target) {
140 - if (!empty($fluentCartCustomer[$source])) {
141 - $customerData[$target] = $fluentCartCustomer[$source];
144 + $details = [];
145 + if ($renewal) {
146 + /* translators: %d: number of renewal payments */
147 + $details[] = sprintf(_n('%d renewal', '%d renewals', $renewal['count'], 'fluent-support'), $renewal['count']);
142 148 }
143 - }
149 + if ($subscription && $subscription->status === 'active' && $subscription->next_billing_date) {
150 + /* translators: %s: date */
151 + $details[] = sprintf(__('Next renewal %s', 'fluent-support'), wp_date(get_option('date_format'), strtotime($subscription->next_billing_date . ' UTC')));
152 + } elseif ($subscription && $subscription->expire_at && strtotime($subscription->expire_at . ' UTC') < time()) {
153 + /* translators: %s: date */
154 + $details[] = sprintf(__('Expired %s', 'fluent-support'), wp_date(get_option('date_format'), strtotime($subscription->expire_at . ' UTC')));
155 + }
144 156
145 - // Use billing address as fallback and add address lines
146 - $billingAddress = $fluentCartCustomer['primary_billing_address'] ?? [];
147 - if (!empty($billingAddress)) {
148 - foreach ($addressMappings as $source => $target) {
149 - if (empty($customerData[$target]) && !empty($billingAddress[$source])) {
150 - $customerData[$target] = $billingAddress[$source];
157 + foreach ($order->order_items as $orderItem) {
158 + if ($orderItem->payment_type == 'signup_fee') {
159 + continue;
151 160 }
152 - }
153 161
154 - foreach (['address_1' => 'address_line_1', 'address_2' => 'address_line_2'] as $source => $target) {
155 - if (!empty($billingAddress[$source])) {
156 - $customerData[$target] = $billingAddress[$source];
157 - }
162 + $variation = $orderItem->title !== $orderItem->post_title ? (string)$orderItem->title : '';
163 +
164 + $items[] = [
165 + 'title' => (string)$orderItem->post_title,
166 + 'subtitle' => $variation
167 + ? sprintf(
168 + /* translators: 1: product variation, 2: order number */
169 + __('%1$s · #%2$s', 'fluent-support'),
170 + $variation,
171 + $order->invoice_no
172 + )
173 + : '#' . $order->invoice_no,
174 + 'detail' => implode(' · ', $details),
175 + 'meta' => self::gmtDateIso($lastPaidAt),
176 + 'meta_type' => 'date',
177 + 'url' => URL::getDashboardUrl('orders/' . $order->id . '/view'),
178 + 'badge' => $badge,
179 + ];
158 180 }
159 181 }
160 182
161 - Customer::maybeCreateCustomer($customerData);
183 + // Latest payment first, so a subscription that just renewed sits at the top.
184 + usort($items, function ($a, $b) {
185 + return strcmp((string)$b['meta'], (string)$a['meta']);
186 + });
187 +
188 + return [
189 + 'stats' => $stats,
190 + 'sections' => [
191 + [
192 + 'label' => __('Recent Purchases', 'fluent-support'),
193 + 'items' => array_slice($items, 0, 10),
194 + ],
195 + ],
196 + ];
162 197 }
163 198
164 199 /**
165 - * Get customer's product licenses from FluentCart Pro
200 + * Cents to a plain-text amount with the currency sign.
166 201 *
167 - * @param Customer $customer
168 - * @return array|null Structured license data or null if not available
202 + * @param int $cents
203 + * @param string $currency
204 + * @return string
169 205 */
170 - private function getCustomerProductLicenses($customer)
206 + private static function formatAmount($cents, $currency)
171 207 {
172 - // Check if FluentCart Pro License model is available
173 - if (!class_exists('\FluentCartPro\App\Modules\Licensing\Models\License')) {
174 - return null;
175 - }
208 + return html_entity_decode(wp_strip_all_tags((string)CartHelper::toDecimal((int)$cents, true, $currency ?: null)), ENT_QUOTES, 'UTF-8');
209 + }
176 210
177 - try {
178 - $wpUserId = $customer->user_id;
179 - if (!$wpUserId) {
180 - return null;
211 + private function findCustomer($customer)
212 + {
213 + if (!empty($customer->email)) {
214 + $cartCustomer = CartCustomer::query()->where('email', $customer->email)->first();
215 + if ($cartCustomer) {
216 + return $cartCustomer;
181 217 }
218 + }
182 219
183 - // Get FluentCart customer ID
184 - $fluentCartCustomer = \FluentCart\App\Models\Customer::where('user_id', $wpUserId)->first();
185 - if (!$fluentCartCustomer) {
186 - return null;
187 - }
220 + if (!empty($customer->user_id)) {
221 + return CartCustomer::query()->where('user_id', $customer->user_id)->first();
222 + }
188 223
189 - // Get licenses for this customer
190 - $licenses = \FluentCartPro\App\Modules\Licensing\Models\License::where('customer_id', $fluentCartCustomer->id)
191 - ->with(['product'])
192 - ->orderByDesc('created_at')
193 - ->get()
194 - ->toArray();
224 + return null;
225 + }
195 226
196 - if (empty($licenses)) {
197 - return null;
198 - }
227 + /**
228 + * Plain purchase data for MCP agents. Amounts are in the currency's major unit;
229 + * dates are ISO 8601 UTC (FluentCart stores GMT).
230 + *
231 + * @param \FluentCart\Framework\Support\Collection $orders
232 + * @return array
233 + */
234 + private function getMcpData($orders)
235 + {
236 + $mcpOrders = [];
237 + $activeSubscriptions = 0;
199 238
200 - $formattedLicenses = [];
201 -
202 - foreach ($licenses as $license) {
203 - // Get product name from the data structure
204 - $productName = 'Unknown Product';
205 - if (isset($license['product']['post_title'])) {
206 - $productName = $license['product']['post_title'];
239 + // The server keeps 50 list items; send the latest 50.
240 + foreach ($orders->take(50) as $order) {
241 + $items = [];
242 + foreach ($order->order_items as $orderItem) {
243 + if ($orderItem->payment_type == 'signup_fee') {
244 + continue;
207 245 }
208 246
247 + $items[] = [
248 + 'product_id' => (int)$orderItem->post_id,
249 + 'name' => (string)$orderItem->post_title,
250 + 'variation' => (string)$orderItem->title,
251 + 'qty' => (int)$orderItem->quantity,
252 + 'total' => $this->toAmount($orderItem->line_total),
253 + ];
254 + }
209 255
210 -
211 - $status = $license['status'];
212 - $expirationDate = $license['expiration_date'];
213 -
214 - // Format expiration date for tooltip
215 - $expirationTooltip = '';
216 - if ($expirationDate) {
217 - $expirationTooltip = 'Expires: ' . date('M d, Y', strtotime($expirationDate));
218 - } else {
219 - $expirationTooltip = 'Expires: Lifetime';
256 + $subscriptions = [];
257 + foreach ($order->subscriptions as $subscription) {
258 + if ($subscription->status === 'active') {
259 + $activeSubscriptions++;
220 260 }
221 261
222 - $formattedLicenses[] = [
223 - 'id' => $license['id'],
224 - 'product_name' => $productName,
225 - 'status' => $status,
226 - 'expiration_tooltip' => $expirationTooltip,
227 - 'expiration_date' => $expirationDate
262 + $subscriptions[] = [
263 + 'id' => (int)$subscription->id,
264 + 'name' => (string)$subscription->item_name,
265 + 'status' => (string)$subscription->status,
266 + 'billing_interval' => (string)$subscription->billing_interval,
267 + 'next_billing_date' => self::gmtDateIso($subscription->next_billing_date),
268 + 'expire_at' => self::gmtDateIso($subscription->expire_at),
228 269 ];
229 270 }
230 271
231 - return $formattedLicenses;
272 + $mcpOrders[] = [
273 + 'id' => (int)$order->id,
274 + 'number' => (string)$order->invoice_no,
275 + 'status' => (string)$order->status,
276 + 'payment_status' => (string)$order->payment_status,
277 + 'total' => $this->toAmount($order->total_amount),
278 + 'paid' => $this->toAmount($order->total_paid),
279 + 'refunded' => $this->toAmount($order->total_refund),
280 + 'currency' => (string)$order->currency,
281 + 'date' => self::gmtDateIso($order->created_at),
282 + 'url' => URL::getDashboardUrl('orders/' . $order->id . '/view'),
283 + 'items' => $items,
284 + 'subscriptions' => $subscriptions,
285 + ];
286 + }
232 287
233 - } catch (\Exception $e) {
234 - return null;
288 + $data = [
289 + 'total_orders' => Order::query()->where('customer_id', $orders->first()->customer_id)->count(),
290 + 'active_subscriptions' => $activeSubscriptions,
291 + 'orders' => $mcpOrders,
292 + ];
293 +
294 + $licenses = $this->getMcpLicenses($orders->first()->customer_id);
295 + if ($licenses) {
296 + $data['licenses'] = $licenses;
235 297 }
298 +
299 + return $data;
236 300 }
237 301
238 302 /**
239 - * Determine the license type based on order item payment type
303 + * FluentCart Pro licenses, without the keys.
240 304 *
241 - * @param \FluentCart\App\Models\OrderItem $item
242 - * @return string|null
305 + * @param int $customerId
306 + * @return array
243 307 */
244 - private function getLicenseType($item)
308 + private function getMcpLicenses($customerId)
245 309 {
246 - switch ($item->payment_type) {
247 - case 'subscription':
248 - // Return 'Subscription' for internal logic but don't display it as license type
249 - return 'Subscription';
250 - case 'payment':
251 - default:
252 - // Check if it's a lifetime license based on product name or other criteria
253 - if (stripos($item->post_title, 'lifetime') !== false || stripos($item->title, 'lifetime') !== false) {
254 - return 'Lifetime License';
255 - }
256 - // Don't show license type for regular one-time purchases
257 - return null;
310 + if (!class_exists('\FluentCartPro\App\Modules\Licensing\Models\License')) {
311 + return [];
258 312 }
313 +
314 + $licenses = \FluentCartPro\App\Modules\Licensing\Models\License::query()
315 + ->where('customer_id', $customerId)
316 + ->with(['product', 'variation'])
317 + ->orderBy('id', 'DESC')
318 + ->limit(50)
319 + ->get();
320 +
321 + $formatted = [];
322 + foreach ($licenses as $license) {
323 + $formatted[] = [
324 + 'id' => (int)$license->id,
325 + 'product' => $license->product ? (string)$license->product->post_title : '',
326 + 'variation' => $license->variation ? (string)$license->variation->variation_title : '',
327 + 'status' => (string)$license->status,
328 + 'is_expired' => $license->isExpired(),
329 + 'expiration_date' => self::gmtDateIso($license->expiration_date),
330 + 'activation_count' => (int)$license->activation_count,
331 + 'limit' => $license->limit ? (int)$license->limit : 'unlimited',
332 + 'order_id' => (int)$license->order_id,
333 + ];
334 + }
335 +
336 + return $formatted;
259 337 }
260 338
339 + // FluentCart stores amounts in cents, zero-decimal currencies too (see CartHelper::toDecimal()).
340 + private function toAmount($cents)
341 + {
342 + return round((float)$cents / 100, CartHelper::shopConfig('is_zero_decimal') ? 0 : 2);
343 + }
344 +
261 345 /**
262 - * Determine the product status based on order status and payment type
346 + * A GMT MySQL datetime as ISO 8601 UTC, or null.
263 347 *
264 - * @param \FluentCart\App\Models\OrderItem $item
265 - * @return string
348 + * @param string|\DateTimeInterface|null $date
349 + * @return string|null
266 350 */
267 - private function getProductStatus($item)
351 + private static function gmtDateIso($date)
268 352 {
269 - $orderStatus = $item->order->status;
353 + if ($date instanceof \DateTimeInterface) {
354 + $date = $date->format('Y-m-d H:i:s');
355 + }
270 356
271 - // Return 'canceled' for multiple statuses
272 - if (in_array($orderStatus, ['canceled', 'failed'])) {
273 - return 'canceled';
357 + if (!$date || strpos((string)$date, '0000-00-00') === 0) {
358 + return null;
274 359 }
275 360
276 - // Return default statuses
277 - $statusMap = [
278 - 'completed' => 'completed',
279 - 'processing' => 'processing',
280 - 'on-hold' => 'on-hold'
281 - ];
361 + $timestamp = strtotime($date . ' UTC');
282 362
283 - return $statusMap[$orderStatus] ?? 'pending'; // Default to 'pending' if not found
363 + return $timestamp ? gmdate('c', $timestamp) : null;
284 364 }
285 365
286 366 private function renderCustomerPortalInFluentCartDashboard()
287 367 {
288 - if (!function_exists('fluent_cart_api') || Helper::getBusinessSettings('enable_fc_menu') != 'yes' ) {
368 + if (!function_exists('fluent_cart_api') || Helper::getBusinessSettings('ticket_link_portal') !== 'fluent_cart' ) {
289 369 return;
290 370 }
291 371
292 372 fluent_cart_api()->addCustomerDashboardEndpoint('fluent-support', [
@@ -293,9 +373,8 @@
293 373 'title' => __('Support', 'fluent-support'),
294 374 'render_callback' => function () {
295 375 echo do_shortcode('[fluent_support_portal]');
296 376 },
297 - 'priority' => 'high',
298 - 'page_id_x' => apply_filters('fluent_support/fc_menu_page_id', 573),
377 + 'priority' => 'high'
299 378 ]);
300 379 }
301 380 }