PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.26
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.26
1.6.6 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 All 49 releases
fluent-cart / app / Hooks / Handlers / ReminderHandler.php

ReminderHandler.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.26, at app/Hooks/Handlers/ReminderHandler.php

335 lines 16.1 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\Hooks\Handlers;
4
5 use FluentCart\App\Models\Order;
6 use FluentCart\App\Models\Subscription;
7 use FluentCart\App\Services\Reminders\InvoiceReminderService;
8 use FluentCart\App\Services\Reminders\ReminderService;
9 use FluentCart\App\Services\Reminders\SubscriptionReminderService;
10 use FluentCart\Framework\Support\Arr;
11
12 class ReminderHandler
13 {
14 public function register(): void
15 {
16 add_action('fluent_cart/scheduler/hourly_tasks', [$this, 'scan'], 40);
17
18 add_action(SubscriptionReminderService::RENEWAL_ASYNC_HOOK, [$this, 'sendSubscriptionRenewalReminder'], 10, 3);
19 add_action(SubscriptionReminderService::TRIAL_ASYNC_HOOK, [$this, 'sendSubscriptionTrialReminder'], 10, 3);
20
21 add_filter('fluent_cart/store_settings/values', [$this, 'addDefaultStoreSettings'], 10, 2);
22 add_filter('fluent_cart/store_settings/fields', [$this, 'addStoreSettingFields'], 10, 2);
23 add_filter('fluent_cart/store_settings/sanitizer', [$this, 'addStoreSettingSanitizers']);
24
25 add_action('fluent_cart/order_paid', [$this, 'clearOrderReminderState'], 10, 1);
26 add_action('fluent_cart/order_refunded', [$this, 'clearOrderReminderState'], 10, 1);
27
28 add_action('fluent_cart/payments/subscription_canceled', [$this, 'clearSubscriptionReminderState'], 10, 1);
29 add_action('fluent_cart/payments/subscription_expired', [$this, 'clearSubscriptionReminderState'], 10, 1);
30 }
31
32 public function scan(): void
33 {
34 (new ReminderService())->runHourlyScan();
35 }
36
37 public function sendSubscriptionRenewalReminder($subscriptionId, $stage, $cycleKey): void
38 {
39 (new SubscriptionReminderService())->sendRenewal($subscriptionId, $stage, $cycleKey);
40 }
41
42 public function sendSubscriptionTrialReminder($subscriptionId, $stage, $cycleKey): void
43 {
44 (new SubscriptionReminderService())->sendTrial($subscriptionId, $stage, $cycleKey);
45 }
46
47 public function addDefaultStoreSettings($defaults): array
48 {
49 if (!is_array($defaults)) {
50 $defaults = [];
51 }
52
53 return wp_parse_args($defaults, [
54 'reminders_enabled' => 'no',
55 // Yearly renewal reminders (enabled by default - compliance)
56 'yearly_renewal_reminders_enabled' => 'yes',
57 'yearly_renewal_reminder_days' => '30',
58 // Trial end reminders (enabled by default - conversion)
59 'trial_end_reminders_enabled' => 'yes',
60 'trial_end_reminder_days' => '3',
61 // Optional additional billing cycles
62 'monthly_renewal_reminders_enabled' => 'no',
63 'monthly_renewal_reminder_days' => '7',
64 'quarterly_renewal_reminders_enabled' => 'no',
65 'quarterly_renewal_reminder_days' => '14',
66 'half_yearly_renewal_reminders_enabled' => 'no',
67 'half_yearly_renewal_reminder_days' => '21',
68 ]);
69 }
70
71 public function addStoreSettingFields($fields): array
72 {
73 $tabsPath = 'setting_tabs.schema';
74 $tabs = Arr::get($fields, $tabsPath, []);
75
76 if (!is_array($tabs)) {
77 return $fields;
78 }
79
80 $enabledCondition = [
81 [
82 'key' => 'reminders_enabled',
83 'operator' => '==',
84 'value' => 'yes',
85 ],
86 ];
87
88 $tabs['reminders'] = [
89 'title' => __('Reminder Emails', 'fluent-cart'),
90 'show_title' => false,
91 'type' => 'section',
92 'columns' => [
93 'default' => 1,
94 'md' => 1
95 ],
96 'disable_nesting' => true,
97 'schema' => [
98 // Master switch row
99 'intro_grid' => [
100 'type' => 'grid',
101 'columns' => [
102 'default' => 1,
103 'md' => 3
104 ],
105 'disable_nesting' => true,
106 'schema' => [
107 'label' => [
108 'type' => 'html',
109 'value' => '<span class="setting-label">' . __('Reminder Emails', 'fluent-cart') . '</span>
110 <div class="form-note">' . __('Send automated reminders for Subscription trial ending and upcoming subscription renewals.', 'fluent-cart') . '</div>'
111 ],
112 'reminders_enabled' => [
113 'wrapperClass' => 'col-span-2 flex items-start justify-end',
114 'label' => false,
115 'type' => 'switch',
116 'value' => 'no',
117 'attributes' => [
118 'active-value' => 'yes',
119 'inactive-value' => 'no',
120 ],
121 ],
122 ],
123 ],
124
125 'email_notice' => [
126 'conditions' => $enabledCondition,
127 'type' => 'html',
128 'value' => '<div class="fc_reminder_email_notice" style="background: #f0f6ff; border: 1px solid #d0e2ff; border-radius: 6px; padding: 10px 14px; font-size: 13px; color: #1e3a5f;">'
129 . sprintf(
130 /* translators: %s is the opening and closing anchor tag */
131 __('Reminder emails must also be enabled in %1$sEmail Notification Settings%2$s under "Scheduler / Reminder Actions" to be delivered.', 'fluent-cart'),
132 '<a href="#/settings/email_notifications" style="color: #017EF3; text-decoration: underline;">',
133 '</a>'
134 )
135 . '</div>'
136 ],
137
138 // ── Subscription Reminders Group ──
139 'subscription_hr' => [
140 'conditions' => $enabledCondition,
141 'type' => 'html',
142 'value' => '<hr class="settings-divider">'
143 ],
144 'subscription_group_label' => [
145 'conditions' => $enabledCondition,
146 'type' => 'html',
147 'wrapperClass' => 'mb-4',
148 'value' => '<span class="setting-label" style="font-size: 15px;">' . __('Subscription Reminders', 'fluent-cart') . '</span>
149 <div class="form-note">' . __('Send reminders before subscription renewals and trial expirations.', 'fluent-cart') . '</div>'
150 ],
151 'trial_inner_hr' => [
152 'conditions' => $enabledCondition,
153 'type' => 'html',
154 'value' => '<hr class="settings-divider">'
155 ],
156 'trial_grid' => [
157 'conditions' => $enabledCondition,
158 'type' => 'grid',
159 'columns' => [
160 'default' => 1,
161 'md' => 3
162 ],
163 'disable_nesting' => true,
164 'schema' => [
165 'label' => [
166 'type' => 'html',
167 'value' => '<span class="setting-label">' . __('Trial Ending', 'fluent-cart') . '</span>
168 <div class="form-note">' . __('Notify customers before their trial ends and billing begins.', 'fluent-cart') . '</div>'
169 ],
170 'trial_input_grid' => [
171 'type' => 'grid',
172 'columns' => ['default' => 1, 'md' => 1],
173 'disable_nesting' => true,
174 'class' => 'col-span-2',
175 'schema' => [
176 'trial_end_reminders_enabled' => [
177 'label' => __('Enable', 'fluent-cart'),
178 'type' => 'checkbox',
179 'value' => 'yes',
180 ],
181 'trial_end_reminder_days' => [
182 'label' => false,
183 'type' => 'input',
184 'value' => '3',
185 'note' => __('Days before trial ends. Min: 1, Max: 14. Default: 3', 'fluent-cart'),
186 'conditions' => [['key' => 'trial_end_reminders_enabled', 'operator' => '==', 'value' => 'yes']],
187 ],
188 ],
189 ],
190 ],
191 ],
192 'renewal_inner_hr' => [
193 'conditions' => $enabledCondition,
194 'type' => 'html',
195 'value' => '<hr class="settings-divider">'
196 ],
197 'renewal_grid' => [
198 'conditions' => $enabledCondition,
199 'type' => 'grid',
200 'columns' => [
201 'default' => 1,
202 'md' => 3
203 ],
204 'disable_nesting' => true,
205 'schema' => [
206 'label' => [
207 'type' => 'html',
208 'value' => '<span class="setting-label">' . __('Renewal Reminders', 'fluent-cart') . '</span>
209 <div class="form-note">' . __('Notify customers before upcoming subscription renewals.', 'fluent-cart') . '</div>'
210 ],
211 'renewal_input_grid' => [
212 'type' => 'grid',
213 'columns' => ['default' => 1, 'md' => 1],
214 'disable_nesting' => true,
215 'class' => 'col-span-2',
216 'schema' => [
217 'yearly_renewal_reminders_enabled' => [
218 'label' => __('Yearly (Recommended)', 'fluent-cart'),
219 'type' => 'checkbox',
220 'value' => 'yes',
221 ],
222 'yearly_renewal_reminder_days' => [
223 'label' => false,
224 'type' => 'input',
225 'value' => '30',
226 'wrapperClass' => 'mb-6',
227 'note' => __('Days before billing date. Min: 7, Max: 90. Default: 30', 'fluent-cart'),
228 'conditions' => [['key' => 'yearly_renewal_reminders_enabled', 'operator' => '==', 'value' => 'yes']],
229 ],
230 'half_yearly_renewal_reminders_enabled' => [
231 'label' => __('Half Yearly', 'fluent-cart'),
232 'type' => 'checkbox',
233 'value' => 'no',
234 ],
235 'half_yearly_renewal_reminder_days' => [
236 'label' => false,
237 'type' => 'input',
238 'value' => '21',
239 'wrapperClass' => 'mb-6',
240 'note' => __('Days before half yearly renewal. Min: 7, Max: 60. Default: 21', 'fluent-cart'),
241 'conditions' => [['key' => 'half_yearly_renewal_reminders_enabled', 'operator' => '==', 'value' => 'yes']],
242 ],
243 'quarterly_renewal_reminders_enabled' => [
244 'label' => __('Quarterly', 'fluent-cart'),
245 'type' => 'checkbox',
246 'value' => 'no',
247 ],
248 'quarterly_renewal_reminder_days' => [
249 'label' => false,
250 'type' => 'input',
251 'value' => '14',
252 'wrapperClass' => 'mb-6',
253 'note' => __('Days before quarterly renewal. Min: 7, Max: 60. Default: 14', 'fluent-cart'),
254 'conditions' => [['key' => 'quarterly_renewal_reminders_enabled', 'operator' => '==', 'value' => 'yes']],
255 ],
256 'monthly_renewal_reminders_enabled' => [
257 'label' => __('Monthly', 'fluent-cart'),
258 'type' => 'checkbox',
259 'value' => 'no',
260 ],
261 'monthly_renewal_reminder_days' => [
262 'label' => false,
263 'type' => 'input',
264 'value' => '7',
265 'wrapperClass' => 'mb-6',
266 'note' => __('Days before monthly renewal. Min: 3, Max: 28. Default: 7', 'fluent-cart'),
267 'conditions' => [['key' => 'monthly_renewal_reminders_enabled', 'operator' => '==', 'value' => 'yes']],
268 ],
269 ],
270 ],
271 ],
272 ],
273 ],
274 ];
275
276 Arr::set($fields, $tabsPath, $tabs);
277 return $fields;
278 }
279
280 public function addStoreSettingSanitizers($sanitizers): array
281 {
282 if (!is_array($sanitizers)) {
283 $sanitizers = [];
284 }
285
286 $sanitizers['reminders_enabled'] = 'sanitize_text_field';
287
288 // Yearly renewal reminder sanitizers (default)
289 $sanitizers['yearly_renewal_reminders_enabled'] = 'sanitize_text_field';
290 $sanitizers['yearly_renewal_reminder_days'] = 'intval';
291
292 // Trial end reminder sanitizers (default)
293 $sanitizers['trial_end_reminders_enabled'] = 'sanitize_text_field';
294 $sanitizers['trial_end_reminder_days'] = 'intval';
295
296 // Optional billing cycle reminder sanitizers
297 $sanitizers['monthly_renewal_reminders_enabled'] = 'sanitize_text_field';
298 $sanitizers['monthly_renewal_reminder_days'] = 'intval';
299 $sanitizers['quarterly_renewal_reminders_enabled'] = 'sanitize_text_field';
300 $sanitizers['quarterly_renewal_reminder_days'] = 'intval';
301 $sanitizers['half_yearly_renewal_reminders_enabled'] = 'sanitize_text_field';
302 $sanitizers['half_yearly_renewal_reminder_days'] = 'intval';
303
304 return $sanitizers;
305 }
306
307 public function clearOrderReminderState($data): void
308 {
309 try {
310 $order = Arr::get($data, 'order');
311 if (!$order instanceof Order) {
312 return;
313 }
314
315 (new InvoiceReminderService())->clearState($order);
316 } catch (\Throwable $e) {
317 fluent_cart_error_log('Reminder state clear error', $e->getMessage());
318 }
319 }
320
321 public function clearSubscriptionReminderState($data): void
322 {
323 try {
324 $subscription = Arr::get($data, 'subscription');
325 if (!$subscription instanceof Subscription) {
326 return;
327 }
328
329 (new SubscriptionReminderService())->clearState($subscription);
330 } catch (\Throwable $e) {
331 fluent_cart_error_log('Subscription reminder state clear error', $e->getMessage());
332 }
333 }
334 }
335